package cli

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

	"git.eeqj.de/sneak/secret/internal/secret"
	"git.eeqj.de/sneak/secret/internal/vault"
	"git.eeqj.de/sneak/secret/pkg/agehd"
	"github.com/spf13/afero"
	"github.com/spf13/cobra"
	"github.com/tyler-smith/go-bip39"
)

func newVaultCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "vault",
		Short: "Manage vaults",
		Long:  `Create, list, and select vaults for organizing secrets.`,
	}

	cmd.AddCommand(newVaultListCmd())
	cmd.AddCommand(newVaultCreateCmd())
	cmd.AddCommand(newVaultSelectCmd())
	cmd.AddCommand(newVaultImportCmd())

	return cmd
}

func newVaultListCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "list",
		Short: "List available vaults",
		RunE: func(cmd *cobra.Command, args []string) error {
			jsonOutput, _ := cmd.Flags().GetBool("json")

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

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

func newVaultCreateCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "create <name>",
		Short: "Create a new vault",
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			cli := NewCLIInstance()
			return cli.CreateVault(args[0])
		},
	}
}

func newVaultSelectCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "select <name>",
		Short: "Select a vault as current",
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			cli := NewCLIInstance()
			return cli.SelectVault(args[0])
		},
	}
}

func newVaultImportCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "import <vault-name>",
		Short: "Import a mnemonic into a vault",
		Long:  `Import a BIP39 mnemonic phrase into the specified vault (default if not specified).`,
		Args:  cobra.MaximumNArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			vaultName := "default"
			if len(args) > 0 {
				vaultName = args[0]
			}

			cli := NewCLIInstance()
			return cli.VaultImport(vaultName)
		},
	}
}

// ListVaults lists all available vaults
func (cli *CLIInstance) ListVaults(jsonOutput bool) error {
	vaults, err := vault.ListVaults(cli.fs, cli.stateDir)
	if err != nil {
		return err
	}

	if jsonOutput {
		// Get current vault name for context
		currentVault := ""
		if currentVlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir); err == nil {
			currentVault = currentVlt.GetName()
		}

		result := map[string]interface{}{
			"vaults":        vaults,
			"current_vault": currentVault,
		}

		jsonBytes, err := json.MarshalIndent(result, "", "  ")
		if err != nil {
			return err
		}
		fmt.Println(string(jsonBytes))
	} else {
		// Text output
		fmt.Println("Available vaults:")
		if len(vaults) == 0 {
			fmt.Println("  (none)")
		} else {
			// Try to get current vault for marking
			currentVault := ""
			if currentVlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir); err == nil {
				currentVault = currentVlt.GetName()
			}

			for _, vaultName := range vaults {
				if vaultName == currentVault {
					fmt.Printf("  %s (current)\n", vaultName)
				} else {
					fmt.Printf("  %s\n", vaultName)
				}
			}
		}
	}

	return nil
}

// CreateVault creates a new vault
func (cli *CLIInstance) CreateVault(name string) error {
	secret.Debug("Creating new vault", "name", name, "state_dir", cli.stateDir)

	vlt, err := vault.CreateVault(cli.fs, cli.stateDir, name)
	if err != nil {
		return err
	}

	fmt.Printf("Created vault '%s'\n", vlt.GetName())
	return nil
}

// SelectVault selects a vault as the current one
func (cli *CLIInstance) SelectVault(name string) error {
	if err := vault.SelectVault(cli.fs, cli.stateDir, name); err != nil {
		return err
	}

	fmt.Printf("Selected vault '%s' as current\n", name)
	return nil
}

// VaultImport imports a mnemonic into a specific vault
func (cli *CLIInstance) VaultImport(vaultName string) error {
	secret.Debug("Importing mnemonic into vault", "vault_name", vaultName, "state_dir", cli.stateDir)

	// Get the specific vault by name
	vlt := vault.NewVault(cli.fs, cli.stateDir, vaultName)

	// Check if vault exists
	vaultDir, err := vlt.GetDirectory()
	if err != nil {
		return err
	}

	exists, err := afero.DirExists(cli.fs, vaultDir)
	if err != nil {
		return fmt.Errorf("failed to check if vault exists: %w", err)
	}
	if !exists {
		return fmt.Errorf("vault '%s' does not exist", vaultName)
	}

	// Check if vault already has a public key
	pubKeyPath := fmt.Sprintf("%s/pub.age", vaultDir)
	if _, err := cli.fs.Stat(pubKeyPath); err == nil {
		return fmt.Errorf("vault '%s' already has a long-term key configured", vaultName)
	}

	// Get mnemonic from environment
	mnemonic := os.Getenv(secret.EnvMnemonic)
	if mnemonic == "" {
		return fmt.Errorf("SB_SECRET_MNEMONIC environment variable not set")
	}

	// Validate the mnemonic
	mnemonicWords := strings.Fields(mnemonic)
	secret.Debug("Validating BIP39 mnemonic", "word_count", len(mnemonicWords))
	if !bip39.IsMnemonicValid(mnemonic) {
		return fmt.Errorf("invalid BIP39 mnemonic")
	}

	// Get the next available derivation index for this mnemonic
	derivationIndex, err := vault.GetNextDerivationIndex(cli.fs, cli.stateDir, mnemonic)
	if err != nil {
		secret.Debug("Failed to get next derivation index", "error", err)
		return fmt.Errorf("failed to get next derivation index: %w", err)
	}
	secret.Debug("Using derivation index", "index", derivationIndex)

	// Derive long-term key from mnemonic with the appropriate index
	secret.Debug("Deriving long-term key from mnemonic", "index", derivationIndex)
	ltIdentity, err := agehd.DeriveIdentity(mnemonic, derivationIndex)
	if err != nil {
		return fmt.Errorf("failed to derive long-term key: %w", err)
	}

	// Store long-term public key in vault
	ltPublicKey := ltIdentity.Recipient().String()
	secret.Debug("Storing long-term public key", "pubkey", ltPublicKey, "vault_dir", vaultDir)

	if err := afero.WriteFile(cli.fs, pubKeyPath, []byte(ltPublicKey), 0600); err != nil {
		return fmt.Errorf("failed to store long-term public key: %w", err)
	}

	// Calculate public key hash from index 0 (same for all vaults with this mnemonic)
	// This is used to identify which vaults belong to the same mnemonic family
	identity0, err := agehd.DeriveIdentity(mnemonic, 0)
	if err != nil {
		return fmt.Errorf("failed to derive identity for index 0: %w", err)
	}
	publicKeyHash := vault.ComputeDoubleSHA256([]byte(identity0.Recipient().String()))

	// Load existing metadata
	existingMetadata, err := vault.LoadVaultMetadata(cli.fs, vaultDir)
	if err != nil {
		// If metadata doesn't exist, create new
		existingMetadata = &vault.VaultMetadata{
			Name:      vaultName,
			CreatedAt: time.Now(),
		}
	}

	// Update metadata with new derivation info
	existingMetadata.DerivationIndex = derivationIndex
	existingMetadata.PublicKeyHash = publicKeyHash

	if err := vault.SaveVaultMetadata(cli.fs, vaultDir, existingMetadata); err != nil {
		secret.Debug("Failed to save vault metadata", "error", err)
		return fmt.Errorf("failed to save vault metadata: %w", err)
	}
	secret.Debug("Saved vault metadata with derivation index and public key hash")

	// Get passphrase from environment variable
	passphraseStr := os.Getenv(secret.EnvUnlockPassphrase)
	if passphraseStr == "" {
		return fmt.Errorf("SB_UNLOCK_PASSPHRASE environment variable not set")
	}

	secret.Debug("Using unlock passphrase from environment variable")

	// Unlock the vault with the derived long-term key
	vlt.Unlock(ltIdentity)

	// Create passphrase-protected unlocker
	secret.Debug("Creating passphrase-protected unlocker")
	passphraseUnlocker, err := vlt.CreatePassphraseUnlocker(passphraseStr)
	if err != nil {
		secret.Debug("Failed to create unlocker", "error", err)
		return fmt.Errorf("failed to create unlocker: %w", err)
	}

	fmt.Printf("Successfully imported mnemonic into vault '%s'\n", vaultName)
	fmt.Printf("Long-term public key: %s\n", ltPublicKey)
	fmt.Printf("Unlocker ID: %s\n", passphraseUnlocker.GetID())

	return nil
}
