package sftp

import (
	"fmt"
	"io"
	"os"
	"sort"
	"strings"
	"sync"

	"github.com/pkg/sftp"
)

const MaxEditFileSize = 5 * 1024 * 1024 // 5MB

// FileEntry represents a file or directory in a remote filesystem.
type FileEntry struct {
	Name        string `json:"name"`
	Path        string `json:"path"`
	Size        int64  `json:"size"`
	IsDir       bool   `json:"isDir"`
	Permissions string `json:"permissions"`
	ModTime     string `json:"modTime"`
}

// SFTPService manages SFTP clients keyed by session ID.
type SFTPService struct {
	clients map[string]*sftp.Client
	mu      sync.RWMutex
}

// NewSFTPService creates a new SFTPService.
func NewSFTPService() *SFTPService {
	return &SFTPService{
		clients: make(map[string]*sftp.Client),
	}
}

// RegisterClient stores an SFTP client for a session.
func (s *SFTPService) RegisterClient(sessionID string, client *sftp.Client) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.clients[sessionID] = client
}

// RemoveClient removes and closes an SFTP client.
func (s *SFTPService) RemoveClient(sessionID string) {
	s.mu.Lock()
	client, ok := s.clients[sessionID]
	if ok {
		delete(s.clients, sessionID)
	}
	s.mu.Unlock()

	if ok && client != nil {
		client.Close()
	}
}

// getClient returns the SFTP client for a session or an error if not found.
func (s *SFTPService) getClient(sessionID string) (*sftp.Client, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	client, ok := s.clients[sessionID]
	if !ok {
		return nil, fmt.Errorf("no SFTP client for session %s", sessionID)
	}
	return client, nil
}

// SortEntries sorts file entries with directories first, then alphabetically by name.
func SortEntries(entries []FileEntry) {
	sort.Slice(entries, func(i, j int) bool {
		if entries[i].IsDir != entries[j].IsDir {
			return entries[i].IsDir
		}
		return strings.ToLower(entries[i].Name) < strings.ToLower(entries[j].Name)
	})
}

// fileInfoToEntry converts an os.FileInfo and its path into a FileEntry.
func fileInfoToEntry(info os.FileInfo, path string) FileEntry {
	return FileEntry{
		Name:        info.Name(),
		Path:        path,
		Size:        info.Size(),
		IsDir:       info.IsDir(),
		Permissions: info.Mode().Perm().String(),
		ModTime:     info.ModTime().UTC().Format("2006-01-02T15:04:05Z"),
	}
}

// List returns directory contents sorted (dirs first, then files alphabetically).
func (s *SFTPService) List(sessionID string, path string) ([]FileEntry, error) {
	client, err := s.getClient(sessionID)
	if err != nil {
		return nil, err
	}

	infos, err := client.ReadDir(path)
	if err != nil {
		return nil, fmt.Errorf("read directory %s: %w", path, err)
	}

	entries := make([]FileEntry, 0, len(infos))
	for _, info := range infos {
		entryPath := path
		if !strings.HasSuffix(entryPath, "/") {
			entryPath += "/"
		}
		entryPath += info.Name()
		entries = append(entries, fileInfoToEntry(info, entryPath))
	}

	SortEntries(entries)
	return entries, nil
}

// ReadFile reads a file (max 5MB). Returns content as string.
func (s *SFTPService) ReadFile(sessionID string, path string) (string, error) {
	client, err := s.getClient(sessionID)
	if err != nil {
		return "", err
	}

	info, err := client.Stat(path)
	if err != nil {
		return "", fmt.Errorf("stat %s: %w", path, err)
	}

	if info.IsDir() {
		return "", fmt.Errorf("%s is a directory", path)
	}

	if info.Size() > MaxEditFileSize {
		return "", fmt.Errorf("file %s is %d bytes, exceeds max edit size of %d bytes", path, info.Size(), MaxEditFileSize)
	}

	f, err := client.Open(path)
	if err != nil {
		return "", fmt.Errorf("open %s: %w", path, err)
	}
	defer f.Close()

	data, err := io.ReadAll(f)
	if err != nil {
		return "", fmt.Errorf("read %s: %w", path, err)
	}

	return string(data), nil
}

// WriteFile writes content to a file.
func (s *SFTPService) WriteFile(sessionID string, path string, content string) error {
	client, err := s.getClient(sessionID)
	if err != nil {
		return err
	}

	f, err := client.Create(path)
	if err != nil {
		return fmt.Errorf("create %s: %w", path, err)
	}
	defer f.Close()

	if _, err := f.Write([]byte(content)); err != nil {
		return fmt.Errorf("write %s: %w", path, err)
	}

	return nil
}

// Mkdir creates a directory.
func (s *SFTPService) Mkdir(sessionID string, path string) error {
	client, err := s.getClient(sessionID)
	if err != nil {
		return err
	}

	if err := client.Mkdir(path); err != nil {
		return fmt.Errorf("mkdir %s: %w", path, err)
	}
	return nil
}

// Delete removes a file or empty directory.
func (s *SFTPService) Delete(sessionID string, path string) error {
	client, err := s.getClient(sessionID)
	if err != nil {
		return err
	}

	info, err := client.Stat(path)
	if err != nil {
		return fmt.Errorf("stat %s: %w", path, err)
	}

	if info.IsDir() {
		if err := client.RemoveDirectory(path); err != nil {
			return fmt.Errorf("remove directory %s: %w", path, err)
		}
	} else {
		if err := client.Remove(path); err != nil {
			return fmt.Errorf("remove %s: %w", path, err)
		}
	}

	return nil
}

// Rename renames/moves a file.
func (s *SFTPService) Rename(sessionID string, oldPath, newPath string) error {
	client, err := s.getClient(sessionID)
	if err != nil {
		return err
	}

	if err := client.Rename(oldPath, newPath); err != nil {
		return fmt.Errorf("rename %s to %s: %w", oldPath, newPath, err)
	}
	return nil
}

// Stat returns info about a file/directory.
func (s *SFTPService) Stat(sessionID string, path string) (*FileEntry, error) {
	client, err := s.getClient(sessionID)
	if err != nil {
		return nil, err
	}

	info, err := client.Stat(path)
	if err != nil {
		return nil, fmt.Errorf("stat %s: %w", path, err)
	}

	entry := fileInfoToEntry(info, path)
	return &entry, nil
}
