package user

import (
	"crypto/subtle"
	"encoding/json"
	"fmt"
	"os"
	"path"
	"sync"

	"golang.org/x/crypto/ssh"
)

// FileStore implements Store using a JSON file
type FileStore struct {
	filePath string
	users    map[string]*User
	mu       sync.RWMutex
}

// Store defines the interface for user storage and retrieval
type Store interface {
	// GetUser retrieves a user by username
	GetUser(username string) (*User, error)

	// ValidatePassword checks if the password is valid for the user
	ValidatePassword(username string, password []byte) bool

	// ValidatePublicKey checks if the public key is valid for the user
	ValidatePublicKey(username string, keyData string) bool

	// GetUserPermissions returns the permissions for a user on a path
	GetUserPermissions(username string, path string) []string

	// SaveUser saves or updates a user
	SaveUser(user *User) error

	// DeleteUser removes a user
	DeleteUser(username string) error

	// ListUsers returns all usernames
	ListUsers() ([]string, error)
}

// UserNotFoundError is returned when a user is not found
type UserNotFoundError struct {
	Username string
}

func (e *UserNotFoundError) Error() string {
	return fmt.Sprintf("user not found: %s", e.Username)
}

// NewFileStore creates a new user store from a JSON file
func NewFileStore(filePath string) (*FileStore, error) {
	store := &FileStore{
		filePath: filePath,
		users:    make(map[string]*User),
	}

	// Create the file if it doesn't exist
	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		// Create an empty users array
		if err := os.WriteFile(filePath, []byte("[]"), 0600); err != nil {
			return nil, fmt.Errorf("failed to create user store file: %w", err)
		}
	}

	if err := store.loadUsers(); err != nil {
		return nil, err
	}

	return store, nil
}

// loadUsers loads users from the JSON file
func (s *FileStore) loadUsers() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	data, err := os.ReadFile(s.filePath)
	if err != nil {
		return fmt.Errorf("failed to read user store file: %w", err)
	}

	var users []*User
	if err := json.Unmarshal(data, &users); err != nil {
		return fmt.Errorf("failed to parse user store file: %w", err)
	}

	// Clear existing users and add the loaded ones
	s.users = make(map[string]*User)
	for _, user := range users {
		// Process public keys to ensure they're in the correct format
		for i, keyData := range user.PublicKeys {
			// Try to parse the key as an authorized key format
			pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keyData))
			if err == nil {
				// If successful, store the marshaled binary format
				user.PublicKeys[i] = string(pubKey.Marshal())
			}
		}
		// Clean HomeDir to handle trailing slashes and normalize path
		if user.HomeDir != "" {
			user.HomeDir = path.Clean(user.HomeDir)
		}
		s.users[user.Username] = user

	}

	return nil
}

// saveUsers saves users to the JSON file
func (s *FileStore) saveUsers() error {
	s.mu.RLock()
	defer s.mu.RUnlock()

	// Convert map to slice for JSON serialization
	var users []*User
	for _, user := range s.users {
		users = append(users, user)
	}

	data, err := json.MarshalIndent(users, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to serialize users: %w", err)
	}

	if err := os.WriteFile(s.filePath, data, 0600); err != nil {
		return fmt.Errorf("failed to write user store file: %w", err)
	}

	return nil
}

// GetUser returns a user by username
func (s *FileStore) GetUser(username string) (*User, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	user, ok := s.users[username]
	if !ok {
		return nil, &UserNotFoundError{Username: username}
	}

	return user, nil
}

// ValidatePassword checks if the password is valid for the user
func (s *FileStore) ValidatePassword(username string, password []byte) bool {
	user, err := s.GetUser(username)
	if err != nil {
		return false
	}

	// An empty stored or supplied password is never a match: a public-key-only
	// user has no password, and equal-length zero slices would otherwise compare equal.
	if len(user.Password) == 0 || len(password) == 0 {
		return false
	}
	// Compare plaintext password using constant time comparison for security
	return subtle.ConstantTimeCompare([]byte(user.Password), password) == 1
}

// ValidatePublicKey checks if the public key is valid for the user
func (s *FileStore) ValidatePublicKey(username string, keyData string) bool {
	user, err := s.GetUser(username)
	if err != nil {
		return false
	}

	for _, key := range user.PublicKeys {
		if subtle.ConstantTimeCompare([]byte(key), []byte(keyData)) == 1 {
			return true
		}
	}

	return false
}

// GetUserPermissions returns the permissions for a user on a path
func (s *FileStore) GetUserPermissions(username string, path string) []string {
	user, err := s.GetUser(username)
	if err != nil {
		return nil
	}

	// Check exact path match first
	if perms, ok := user.Permissions[path]; ok {
		return perms
	}

	// Check parent directories
	var bestMatch string
	var bestPerms []string

	for p, perms := range user.Permissions {
		if len(p) > len(bestMatch) && os.IsPathSeparator(p[len(p)-1]) && path[:len(p)] == p {
			bestMatch = p
			bestPerms = perms
		}
	}

	return bestPerms
}

// SaveUser saves or updates a user
func (s *FileStore) SaveUser(user *User) error {
	s.mu.Lock()
	s.users[user.Username] = user
	s.mu.Unlock()

	return s.saveUsers()
}

// DeleteUser removes a user
func (s *FileStore) DeleteUser(username string) error {
	s.mu.Lock()
	_, exists := s.users[username]
	if !exists {
		s.mu.Unlock()
		return &UserNotFoundError{Username: username}
	}

	delete(s.users, username)
	s.mu.Unlock()

	return s.saveUsers()
}

// ListUsers returns all usernames
func (s *FileStore) ListUsers() ([]string, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	usernames := make([]string, 0, len(s.users))
	for username := range s.users {
		usernames = append(usernames, username)
	}

	return usernames, nil
}

// CreateUser creates a new user with the given username and password
func (s *FileStore) CreateUser(username, password string) (*User, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	// Check if user already exists
	if _, exists := s.users[username]; exists {
		return nil, fmt.Errorf("user already exists: %s", username)
	}

	// Create new user
	user := NewUser(username)

	// Store plaintext password
	user.Password = password

	// Add default permissions
	user.Permissions[user.HomeDir] = []string{"all"}

	// Save the user
	s.users[username] = user
	if err := s.saveUsers(); err != nil {
		return nil, err
	}

	return user, nil
}

// Reload reloads users from the file, useful for HUP signal handling
func (s *FileStore) Reload() error {
	return s.loadUsers()
}
