// Package keyring provides secure password storage for sudo authentication
// in Web UI mode. It supports multiple backends including:
// - Linux: SecretService (GNOME Keyring), KWallet, pass, file-based
// - macOS: Keychain (native)
// - Windows: Credential Manager (native)
package keyring

import (
	"bufio"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"runtime"
	"strconv"
	"strings"

	"github.com/99designs/keyring"

	"gitgud.io/mike/mpv-manager/pkg/log"
)

const (
	// ServiceName is the application identifier for the keyring
	ServiceName = "mpv-manager"

	// PasswordKey is the key used to store the sudo password
	PasswordKey = "sudo-password"
)

// BackendType represents a keyring backend
type BackendType string

const (
	// BackendSecretService is the GNOME Keyring/libsecret backend
	BackendSecretService BackendType = "secret-service"

	// BackendKWallet is the KDE Wallet backend
	BackendKWallet BackendType = "kwallet"

	// BackendPass is the pass (password-store) backend
	BackendPass BackendType = "pass"

	// BackendFile is the encrypted file-based backend
	BackendFile BackendType = "file"

	// BackendNone indicates no backend is available
	BackendNone BackendType = "none"
)

// Status contains information about keyring availability and configuration
type Status struct {
	// AvailableBackends lists all backends detected on the system
	AvailableBackends []BackendType `json:"availableBackends"`

	// ActiveBackend is the backend currently being used
	ActiveBackend BackendType `json:"activeBackend"`

	// DaemonRunning indicates if a keyring daemon is running
	DaemonRunning bool `json:"daemonRunning"`

	// InstallHint contains installation command for missing dependencies
	InstallHint string `json:"installHint"`

	// Distro is the detected Linux distribution
	Distro string `json:"distro"`

	// DesktopEnv is the detected desktop environment
	DesktopEnv string `json:"desktopEnv"`

	// HasPassword indicates if a password is currently stored
	HasPassword bool `json:"hasPassword"`

	// Platform is the current operating system
	Platform string `json:"platform"`
}

// Keyring wraps the keyring.Keyring interface with additional functionality
type Keyring struct {
	ring    keyring.Keyring
	backend BackendType
}

// DetectStatus detects available keyring backends and system environment
func DetectStatus() Status {
	log.Debug("Keyring: Detecting status...")

	status := Status{
		Platform:          runtime.GOOS,
		DaemonRunning:     true, // Assume true for non-Linux
		ActiveBackend:     BackendNone,
		AvailableBackends: []BackendType{},
	}

	// Non-Linux platforms have native keyring support
	if runtime.GOOS != "linux" {
		log.Debug("Keyring: Non-Linux platform, using native keyring")
		status.AvailableBackends = []BackendType{BackendFile}
		status.ActiveBackend = BackendFile
		status.Distro = "n/a"
		status.DesktopEnv = "n/a"
		return status
	}

	// Linux-specific detection
	status.Distro = detectDistro()
	status.DesktopEnv = detectDesktopEnvironment()
	log.Debug(fmt.Sprintf("Keyring: Detected distro=%s, desktopEnv=%s", status.Distro, status.DesktopEnv))

	// Detect available backends
	backends := detectAvailableBackends()
	status.AvailableBackends = backends
	log.Debug(fmt.Sprintf("Keyring: Available backends: %v", backends))

	// Determine the active backend - prefer one with a running daemon
	if len(backends) > 0 {
		// First, try to find a backend with a running daemon
		for _, backend := range backends {
			if checkDaemonRunning(backend) {
				status.ActiveBackend = backend
				status.DaemonRunning = true
				break
			}
		}
		// If no running daemon found, use the first available backend
		if status.ActiveBackend == BackendNone {
			status.ActiveBackend = backends[0]
			status.DaemonRunning = checkDaemonRunning(backends[0])
		}
	}
	log.Debug(fmt.Sprintf("Keyring: Active backend: %s, daemon running: %v", status.ActiveBackend, status.DaemonRunning))

	// Generate install hint if no suitable backend found
	// Don't show hint if file backend is available (it's always usable)
	if status.ActiveBackend == BackendNone {
		status.InstallHint = generateInstallHint(status.Distro, status.DesktopEnv)
	} else if !status.DaemonRunning && status.ActiveBackend != BackendFile && status.ActiveBackend != BackendPass {
		// Only show hint for daemon-based backends that aren't running
		status.InstallHint = generateInstallHint(status.Distro, status.DesktopEnv)
	}

	// Check if password is stored
	kr, err := Open()
	if err == nil {
		status.HasPassword = kr.HasPassword()
	}
	log.Debug(fmt.Sprintf("Keyring: Status complete - hasPassword: %v", status.HasPassword))

	return status
}

// Open opens the keyring with backends in priority order
func Open() (*Keyring, error) {
	// Non-Linux platforms use native keyring
	if runtime.GOOS != "linux" {
		ring, err := keyring.Open(keyring.Config{
			ServiceName: ServiceName,
		})
		if err != nil {
			return nil, fmt.Errorf("failed to open keyring: %w", err)
		}
		return &Keyring{ring: ring, backend: BackendFile}, nil
	}

	// Linux: try backends in priority order
	backends := []keyring.BackendType{
		keyring.SecretServiceBackend,
		keyring.KWalletBackend,
		keyring.PassBackend,
		keyring.FileBackend,
	}

	var lastErr error
	for _, backend := range backends {
		config := keyring.Config{
			KeychainTrustApplication: true,
			FilePasswordFunc: func(prompt string) (string, error) {
				// For file backend, use a fixed password based on machine ID
				// This is less secure but allows non-interactive use
				return "mpv-manager-file-keyring", nil
			},
		}

		// Configure backend-specific settings
		switch backend {
		case keyring.KWalletBackend:
			// Use default KDE wallet with our own folder
			config.ServiceName = "kdewallet"
			config.KWalletFolder = "mpv-manager"
			config.KWalletAppID = "mpv-manager"
			log.Debug("Keyring: Trying KWallet backend with kdewallet/mpv-manager folder")
		case keyring.SecretServiceBackend:
			// Use our own collection for SecretService
			config.ServiceName = ServiceName
			log.Debug("Keyring: Trying SecretService backend")
		default:
			config.ServiceName = ServiceName
			log.Debug(fmt.Sprintf("Keyring: Trying %s backend", backend))
		}

		config.AllowedBackends = []keyring.BackendType{backend}

		ring, err := keyring.Open(config)
		if err != nil {
			log.Debug(fmt.Sprintf("Keyring: Backend %s failed: %v", backend, err))
			lastErr = err
			continue
		}

		// Test if backend is functional
		backendType := backendTypeFromKeyring(backend)
		log.Info(fmt.Sprintf("Keyring: Successfully opened backend: %s", backendType))
		return &Keyring{ring: ring, backend: backendType}, nil
	}

	log.Error(fmt.Sprintf("Keyring: No working backend available: %v", lastErr))
	return nil, fmt.Errorf("no working keyring backend available: %w", lastErr)
}

// StorePassword securely stores the sudo password
func (k *Keyring) StorePassword(password string) error {
	if password == "" {
		return errors.New("password cannot be empty")
	}

	log.Debug(fmt.Sprintf("Keyring: Storing password in backend: %s", k.backend))

	err := k.ring.Set(keyring.Item{
		Key:  PasswordKey,
		Data: []byte(password),
	})
	if err != nil {
		log.Error(fmt.Sprintf("Keyring: Failed to store password: %v", err))
		return fmt.Errorf("failed to store password: %w", err)
	}

	log.Info("Keyring: Password stored successfully")
	return nil
}

// GetPassword retrieves the stored sudo password
func (k *Keyring) GetPassword() (string, error) {
	log.Debug(fmt.Sprintf("Keyring: Retrieving password from backend: %s", k.backend))

	item, err := k.ring.Get(PasswordKey)
	if err != nil {
		if errors.Is(err, keyring.ErrKeyNotFound) {
			log.Debug("Keyring: No password stored")
			return "", errors.New("no password stored")
		}
		log.Error(fmt.Sprintf("Keyring: Failed to retrieve password: %v", err))
		return "", fmt.Errorf("failed to retrieve password: %w", err)
	}

	log.Debug("Keyring: Password retrieved successfully")
	return string(item.Data), nil
}

// HasPassword checks if a password is currently stored
func (k *Keyring) HasPassword() bool {
	_, err := k.ring.Get(PasswordKey)
	hasPassword := err == nil
	log.Debug(fmt.Sprintf("Keyring: HasPassword check: %v", hasPassword))
	return hasPassword
}

// DeletePassword removes the stored sudo password
func (k *Keyring) DeletePassword() error {
	err := k.ring.Remove(PasswordKey)
	if err != nil && !errors.Is(err, keyring.ErrKeyNotFound) {
		return fmt.Errorf("failed to delete password: %w", err)
	}
	return nil
}

// Backend returns the currently active backend type
func (k *Keyring) Backend() BackendType {
	return k.backend
}

// detectDistro reads /etc/os-release to determine the Linux distribution
func detectDistro() string {
	if runtime.GOOS != "linux" {
		return "n/a"
	}

	file, err := os.Open("/etc/os-release")
	if err != nil {
		return "unknown"
	}
	defer file.Close()

	var id string
	var idLike string
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		if strings.HasPrefix(line, "ID=") {
			id = strings.Trim(strings.TrimPrefix(line, "ID="), "\"")
		} else if strings.HasPrefix(line, "ID_LIKE=") {
			idLike = strings.Trim(strings.TrimPrefix(line, "ID_LIKE="), "\"")
		}
	}

	// Map to known distro families
	switch id {
	case "ubuntu", "debian", "linuxmint", "pop", "elementary":
		return "debian"
	case "fedora", "rhel", "centos", "rocky", "almalinux":
		return "fedora"
	case "arch", "manjaro", "endeavouros", "garuda":
		return "arch"
	case "opensuse-tumbleweed", "opensuse-leap", "opensuse":
		return "suse"
	}

	// Check ID_LIKE for derivative distros
	if strings.Contains(idLike, "debian") {
		return "debian"
	}
	if strings.Contains(idLike, "fedora") || strings.Contains(idLike, "rhel") {
		return "fedora"
	}
	if strings.Contains(idLike, "arch") {
		return "arch"
	}
	if strings.Contains(idLike, "suse") {
		return "suse"
	}

	return "unknown"
}

// detectDesktopEnvironment determines the current desktop environment
func detectDesktopEnvironment() string {
	if runtime.GOOS != "linux" {
		return "n/a"
	}

	// Check XDG_CURRENT_DESKTOP first
	if de := os.Getenv("XDG_CURRENT_DESKTOP"); de != "" {
		return normalizeDesktopEnv(de)
	}

	// Check DESKTOP_SESSION as fallback
	if session := os.Getenv("DESKTOP_SESSION"); session != "" {
		return normalizeDesktopEnv(session)
	}

	return "unknown"
}

// normalizeDesktopEnv normalizes desktop environment names
func normalizeDesktopEnv(env string) string {
	env = strings.ToLower(env)

	switch {
	case strings.Contains(env, "gnome"):
		return "GNOME"
	case strings.Contains(env, "kde") || strings.Contains(env, "plasma"):
		return "KDE"
	case strings.Contains(env, "xfce"):
		return "XFCE"
	case strings.Contains(env, "lxde") || strings.Contains(env, "lxqt"):
		return "LXDE"
	case strings.Contains(env, "mate"):
		return "MATE"
	case strings.Contains(env, "cinnamon"):
		return "Cinnamon"
	case strings.Contains(env, "budgie"):
		return "Budgie"
	case strings.Contains(env, "deepin"):
		return "Deepin"
	case strings.Contains(env, "pantheon"):
		return "Pantheon"
	case strings.Contains(env, "cosmic"):
		return "COSMIC"
	case strings.Contains(env, "hyprland"):
		return "Hyprland"
	case strings.Contains(env, "river"):
		return "River"
	case strings.Contains(env, "i3"):
		return "i3"
	case strings.Contains(env, "sway"):
		return "Sway"
	case strings.Contains(env, "awesome"):
		return "Awesome"
	case strings.Contains(env, "bspwm"):
		return "bspwm"
	case strings.Contains(env, "dwm"):
		return "dwm"
	case strings.Contains(env, "qtile"):
		return "qtile"
	default:
		return env
	}
}

// generateInstallHint creates installation instructions based on distro and DE
func generateInstallHint(distro, de string) string {
	// Tiling WMs and compositors need special setup
	tilingWMs := map[string]bool{
		"i3": true, "Sway": true, "Hyprland": true, "River": true,
		"Awesome": true, "bspwm": true, "dwm": true, "qtile": true,
	}

	if tilingWMs[de] {
		return "Install gnome-keyring and add 'exec gnome-keyring-daemon --start --components=secrets' to your WM config"
	}

	switch distro {
	case "debian":
		return "sudo apt install gnome-keyring libsecret-1-dev"
	case "fedora":
		return "sudo dnf install gnome-keyring libsecret-devel"
	case "arch":
		return "sudo pacman -S gnome-keyring libsecret"
	case "suse":
		return "sudo zypper install gnome-keyring libsecret-devel"
	default:
		// Generic hint based on desktop environment
		switch de {
		case "KDE":
			return "Install kwallet from your package manager"
		case "GNOME", "Cinnamon", "Pantheon", "XFCE", "MATE", "Budgie", "COSMIC":
			return "Install gnome-keyring and libsecret from your package manager"
		default:
			return "Install a keyring service (gnome-keyring, kwallet, or pass) from your package manager"
		}
	}
}

// detectAvailableBackends checks which backends are available on the system
func detectAvailableBackends() []BackendType {
	backends := []BackendType{}

	// Check for KWallet (KDE5 and KDE6) - check this first for KDE systems
	if checkCommandExists("kwalletd") || checkCommandExists("kwalletd5") || checkCommandExists("kwalletd6") {
		backends = append(backends, BackendKWallet)
	}

	// Check for SecretService (GNOME Keyring / libsecret)
	// Multiple paths for different distros
	secretServicePaths := []string{
		"/usr/bin/gnome-keyring-daemon",
		"/usr/libexec/gnome-keyring-daemon",
		"/usr/lib/gnome-keyring-daemon",
		"/usr/lib64/gnome-keyring-daemon",
	}
	hasSecretService := checkCommandExists("gnome-keyring-daemon")
	if !hasSecretService {
		for _, path := range secretServicePaths {
			if checkFileExists(path) {
				hasSecretService = true
				break
			}
		}
	}
	// Also check for libsecret which is what we actually need
	if !hasSecretService {
		hasSecretService = checkFileExists("/usr/lib/libsecret-1.so") ||
			checkFileExists("/usr/lib64/libsecret-1.so") ||
			checkFileExists("/usr/lib/x86_64-linux-gnu/libsecret-1.so")
	}
	if hasSecretService {
		backends = append(backends, BackendSecretService)
	}

	// Check for pass (must be initialized with a GPG key)
	if checkCommandExists("pass") {
		// Check if pass has been initialized (has .password-store directory)
		homeDir, _ := os.UserHomeDir()
		if homeDir != "" && checkFileExists(homeDir+"/.password-store") {
			backends = append(backends, BackendPass)
		}
	}

	// File backend is always available as fallback
	backends = append(backends, BackendFile)

	return backends
}

// checkDaemonRunning checks if the keyring daemon is running
func checkDaemonRunning(backend BackendType) bool {
	var running bool
	switch backend {
	case BackendSecretService:
		// Check if gnome-keyring-daemon is running
		running = checkProcessRunning("gnome-keyring-d") // truncated name
	case BackendKWallet:
		// Check if kwalletd is running (handles kwalletd, kwalletd5, kwalletd6)
		running = checkProcessRunning("kwalletd")
	case BackendPass:
		// pass doesn't need a daemon
		running = true
	case BackendFile:
		// File backend doesn't need a daemon
		running = true
	default:
		running = false
	}
	log.Debug(fmt.Sprintf("Keyring: checkDaemonRunning(%s) = %v", backend, running))
	return running
}

// checkCommandExists checks if a command exists in PATH
func checkCommandExists(cmd string) bool {
	_, err := exec.LookPath(cmd)
	return err == nil
}

// checkFileExists checks if a file exists
func checkFileExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

// checkProcessRunning checks if a process is running (by name pattern)
func checkProcessRunning(name string) bool {
	// Read /proc to find running processes
	entries, err := os.ReadDir("/proc")
	if err != nil {
		log.Debug(fmt.Sprintf("Keyring: Failed to read /proc: %v", err))
		return false
	}

	for _, entry := range entries {
		if !entry.IsDir() {
			continue
		}

		// Check if directory name is a PID
		pid := entry.Name()
		if _, err := strconv.Atoi(pid); err != nil {
			continue
		}

		// Read the comm file to get process name
		commPath := fmt.Sprintf("/proc/%s/comm", pid)
		data, err := os.ReadFile(commPath)
		if err != nil {
			continue
		}

		procName := strings.TrimSpace(string(data))
		if strings.Contains(procName, name) {
			log.Debug(fmt.Sprintf("Keyring: Found running process '%s' (looking for '%s')", procName, name))
			return true
		}
	}

	log.Debug(fmt.Sprintf("Keyring: No running process found matching '%s'", name))
	return false
}

// backendTypeFromKeyring converts keyring.BackendType to our BackendType
func backendTypeFromKeyring(backend keyring.BackendType) BackendType {
	switch backend {
	case keyring.SecretServiceBackend:
		return BackendSecretService
	case keyring.KWalletBackend:
		return BackendKWallet
	case keyring.PassBackend:
		return BackendPass
	case keyring.FileBackend:
		return BackendFile
	default:
		return BackendNone
	}
}
