package cli

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"git.eeqj.de/sneak/secret/internal/secret"
	"git.eeqj.de/sneak/secret/internal/vault"
	"github.com/spf13/afero"
	"github.com/spf13/cobra"
)

// Import from init.go

// ... existing imports ...

func newUnlockersCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "unlockers",
		Short: "Manage unlockers",
		Long:  `Create, list, and remove unlockers for the current vault.`,
	}

	cmd.AddCommand(newUnlockersListCmd())
	cmd.AddCommand(newUnlockersAddCmd())
	cmd.AddCommand(newUnlockersRmCmd())

	return cmd
}

func newUnlockersListCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "list",
		Short: "List unlockers in the current vault",
		RunE: func(cmd *cobra.Command, args []string) error {
			jsonOutput, _ := cmd.Flags().GetBool("json")

			cli := NewCLIInstance()
			return cli.UnlockersList(jsonOutput)
		},
	}

	cmd.Flags().Bool("json", false, "Output in JSON format")
	return cmd
}

func newUnlockersAddCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "add <type>",
		Short: "Add a new unlocker",
		Long:  `Add a new unlocker of the specified type (passphrase, keychain, pgp).`,
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			cli := NewCLIInstance()
			return cli.UnlockersAdd(args[0], cmd)
		},
	}

	cmd.Flags().String("keyid", "", "GPG key ID for PGP unlockers")
	return cmd
}

func newUnlockersRmCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "rm <unlocker-id>",
		Short: "Remove an unlocker",
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			cli := NewCLIInstance()
			return cli.UnlockersRemove(args[0])
		},
	}
}

func newUnlockerCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "unlocker",
		Short: "Manage current unlocker",
		Long:  `Select the current unlocker for operations.`,
	}

	cmd.AddCommand(newUnlockerSelectSubCmd())

	return cmd
}

func newUnlockerSelectSubCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "select <unlocker-id>",
		Short: "Select an unlocker as current",
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			cli := NewCLIInstance()
			return cli.UnlockerSelect(args[0])
		},
	}
}

// UnlockersList lists unlockers in the current vault
func (cli *CLIInstance) UnlockersList(jsonOutput bool) error {
	// Get current vault
	vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
	if err != nil {
		return err
	}

	// Get the metadata first
	unlockerMetadataList, err := vlt.ListUnlockers()
	if err != nil {
		return err
	}

	// Load actual unlocker objects to get the proper IDs
	type UnlockerInfo struct {
		ID        string    `json:"id"`
		Type      string    `json:"type"`
		CreatedAt time.Time `json:"created_at"`
		Flags     []string  `json:"flags,omitempty"`
	}

	var unlockers []UnlockerInfo
	for _, metadata := range unlockerMetadataList {
		// Create unlocker instance to get the proper ID
		vaultDir, err := vlt.GetDirectory()
		if err != nil {
			continue
		}

		// Find the unlocker directory by type and created time
		unlockersDir := filepath.Join(vaultDir, "unlockers.d")
		files, err := afero.ReadDir(cli.fs, unlockersDir)
		if err != nil {
			continue
		}

		var unlocker secret.Unlocker
		for _, file := range files {
			if !file.IsDir() {
				continue
			}

			unlockerDir := filepath.Join(unlockersDir, file.Name())
			metadataPath := filepath.Join(unlockerDir, "unlocker-metadata.json")

			// Check if this is the right unlocker by comparing metadata
			metadataBytes, err := afero.ReadFile(cli.fs, metadataPath)
			if err != nil {
				continue
			}

			var diskMetadata secret.UnlockerMetadata
			if err := json.Unmarshal(metadataBytes, &diskMetadata); err != nil {
				continue
			}

			// Match by type and creation time
			if diskMetadata.Type == metadata.Type && diskMetadata.CreatedAt.Equal(metadata.CreatedAt) {
				// Create the appropriate unlocker instance
				switch metadata.Type {
				case "passphrase":
					unlocker = secret.NewPassphraseUnlocker(cli.fs, unlockerDir, diskMetadata)
				case "keychain":
					unlocker = secret.NewKeychainUnlocker(cli.fs, unlockerDir, diskMetadata)
				case "pgp":
					unlocker = secret.NewPGPUnlocker(cli.fs, unlockerDir, diskMetadata)
				}
				break
			}
		}

		// Get the proper ID using the unlocker's ID() method
		var properID string
		if unlocker != nil {
			properID = unlocker.GetID()
		} else {
			properID = metadata.ID // fallback to metadata ID
		}

		unlockerInfo := UnlockerInfo{
			ID:        properID,
			Type:      metadata.Type,
			CreatedAt: metadata.CreatedAt,
			Flags:     metadata.Flags,
		}
		unlockers = append(unlockers, unlockerInfo)
	}

	if jsonOutput {
		// JSON output
		output := map[string]interface{}{
			"unlockers": unlockers,
		}

		jsonBytes, err := json.MarshalIndent(output, "", "  ")
		if err != nil {
			return fmt.Errorf("failed to marshal JSON: %w", err)
		}

		fmt.Println(string(jsonBytes))
	} else {
		// Pretty table output
		if len(unlockers) == 0 {
			fmt.Println("No unlockers found in current vault.")
			fmt.Println("Run 'secret unlockers add passphrase' to create one.")
			return nil
		}

		fmt.Printf("%-18s %-12s %-20s %s\n", "UNLOCKER ID", "TYPE", "CREATED", "FLAGS")
		fmt.Printf("%-18s %-12s %-20s %s\n", "-----------", "----", "-------", "-----")

		for _, unlocker := range unlockers {
			flags := ""
			if len(unlocker.Flags) > 0 {
				flags = strings.Join(unlocker.Flags, ",")
			}
			fmt.Printf("%-18s %-12s %-20s %s\n",
				unlocker.ID,
				unlocker.Type,
				unlocker.CreatedAt.Format("2006-01-02 15:04:05"),
				flags)
		}

		fmt.Printf("\nTotal: %d unlocker(s)\n", len(unlockers))
	}

	return nil
}

// UnlockersAdd adds a new unlocker
func (cli *CLIInstance) UnlockersAdd(unlockerType string, cmd *cobra.Command) error {
	switch unlockerType {
	case "passphrase":
		// Get current vault
		vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
		if err != nil {
			return fmt.Errorf("failed to get current vault: %w", err)
		}

		// Try to unlock the vault if not already unlocked
		if vlt.Locked() {
			_, err := vlt.UnlockVault()
			if err != nil {
				return fmt.Errorf("failed to unlock vault: %w", err)
			}
		}

		// Check if passphrase is set in environment variable
		var passphraseStr string
		if envPassphrase := os.Getenv(secret.EnvUnlockPassphrase); envPassphrase != "" {
			passphraseStr = envPassphrase
		} else {
			// Use secure passphrase input with confirmation
			passphraseStr, err = readSecurePassphrase("Enter passphrase for unlocker: ")
			if err != nil {
				return fmt.Errorf("failed to read passphrase: %w", err)
			}
		}

		passphraseUnlocker, err := vlt.CreatePassphraseUnlocker(passphraseStr)
		if err != nil {
			return err
		}

		cmd.Printf("Created passphrase unlocker: %s\n", passphraseUnlocker.GetID())
		return nil

	case "keychain":
		keychainUnlocker, err := secret.CreateKeychainUnlocker(cli.fs, cli.stateDir)
		if err != nil {
			return fmt.Errorf("failed to create macOS Keychain unlocker: %w", err)
		}

		cmd.Printf("Created macOS Keychain unlocker: %s\n", keychainUnlocker.GetID())
		if keyName, err := keychainUnlocker.GetKeychainItemName(); err == nil {
			cmd.Printf("Keychain Item Name: %s\n", keyName)
		}
		return nil

	case "pgp":
		// Get GPG key ID from flag or environment variable
		var gpgKeyID string
		if flagKeyID, _ := cmd.Flags().GetString("keyid"); flagKeyID != "" {
			gpgKeyID = flagKeyID
		} else if envKeyID := os.Getenv(secret.EnvGPGKeyID); envKeyID != "" {
			gpgKeyID = envKeyID
		} else {
			return fmt.Errorf("GPG key ID required: use --keyid flag or set SB_GPG_KEY_ID environment variable")
		}

		pgpUnlocker, err := secret.CreatePGPUnlocker(cli.fs, cli.stateDir, gpgKeyID)
		if err != nil {
			return err
		}

		cmd.Printf("Created PGP unlocker: %s\n", pgpUnlocker.GetID())
		cmd.Printf("GPG Key ID: %s\n", gpgKeyID)
		return nil

	default:
		return fmt.Errorf("unsupported unlocker type: %s (supported: passphrase, keychain, pgp)", unlockerType)
	}
}

// UnlockersRemove removes an unlocker
func (cli *CLIInstance) UnlockersRemove(unlockerID string) error {
	// Get current vault
	vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
	if err != nil {
		return err
	}

	return vlt.RemoveUnlocker(unlockerID)
}

// UnlockerSelect selects an unlocker as current
func (cli *CLIInstance) UnlockerSelect(unlockerID string) error {
	// Get current vault
	vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
	if err != nil {
		return err
	}

	return vlt.SelectUnlocker(unlockerID)
}