package cli

import (
	"fmt"
	"io"
	"os"

	"filippo.io/age"
	"git.eeqj.de/sneak/secret/internal/secret"
	"github.com/spf13/cobra"
)

func newEncryptCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "encrypt <secret-name>",
		Short: "Encrypt data using an age secret key stored in a secret",
		Long:  `Encrypt data using an age secret key. If the secret doesn't exist, a new age key is generated and stored.`,
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			inputFile, _ := cmd.Flags().GetString("input")
			outputFile, _ := cmd.Flags().GetString("output")

			cli := NewCLIInstance()
			return cli.Encrypt(args[0], inputFile, outputFile)
		},
	}

	cmd.Flags().StringP("input", "i", "", "Input file (default: stdin)")
	cmd.Flags().StringP("output", "o", "", "Output file (default: stdout)")
	return cmd
}

func newDecryptCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "decrypt <secret-name>",
		Short: "Decrypt data using an age secret key stored in a secret",
		Long:  `Decrypt data using an age secret key stored in the specified secret.`,
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			inputFile, _ := cmd.Flags().GetString("input")
			outputFile, _ := cmd.Flags().GetString("output")

			cli := NewCLIInstance()
			return cli.Decrypt(args[0], inputFile, outputFile)
		},
	}

	cmd.Flags().StringP("input", "i", "", "Input file (default: stdin)")
	cmd.Flags().StringP("output", "o", "", "Output file (default: stdout)")
	return cmd
}

// Encrypt encrypts data using an age secret key stored in a secret
func (cli *CLIInstance) Encrypt(secretName, inputFile, outputFile string) error {
	// Get current vault
	vault, err := secret.GetCurrentVault(cli.fs, cli.stateDir)
	if err != nil {
		return err
	}

	var ageSecretKey string

	// Check if secret exists
	secretObj := secret.NewSecret(vault, secretName)
	exists, err := secretObj.Exists()
	if err != nil {
		return fmt.Errorf("failed to check if secret exists: %w", err)
	}

	if exists {
		// Secret exists, get the age secret key from it
		var secretValue []byte
		if os.Getenv(secret.EnvMnemonic) != "" {
			secretValue, err = secretObj.GetValue(nil)
		} else {
			unlockKey, unlockErr := vault.GetCurrentUnlockKey()
			if unlockErr != nil {
				return fmt.Errorf("failed to get current unlock key: %w", unlockErr)
			}
			secretValue, err = secretObj.GetValue(unlockKey)
		}
		if err != nil {
			return fmt.Errorf("failed to get secret value: %w", err)
		}

		ageSecretKey = string(secretValue)

		// Validate that it's a valid age secret key
		if !isValidAgeSecretKey(ageSecretKey) {
			return fmt.Errorf("secret '%s' does not contain a valid age secret key", secretName)
		}
	} else {
		// Secret doesn't exist, generate a new age secret key
		identity, err := age.GenerateX25519Identity()
		if err != nil {
			return fmt.Errorf("failed to generate age secret key: %w", err)
		}

		ageSecretKey = identity.String()

		// Store the new secret
		if err := vault.AddSecret(secretName, []byte(ageSecretKey), false); err != nil {
			return fmt.Errorf("failed to store age secret key: %w", err)
		}

		fmt.Fprintf(os.Stderr, "Generated new age secret key and stored in secret '%s'\n", secretName)
	}

	// Parse the age secret key to get the identity
	identity, err := age.ParseX25519Identity(ageSecretKey)
	if err != nil {
		return fmt.Errorf("failed to parse age secret key: %w", err)
	}

	// Get the recipient (public key) for encryption
	recipient := identity.Recipient()

	// Set up input reader
	var input io.Reader = os.Stdin
	if inputFile != "" {
		file, err := cli.fs.Open(inputFile)
		if err != nil {
			return fmt.Errorf("failed to open input file: %w", err)
		}
		defer file.Close()
		input = file
	}

	// Set up output writer
	var output io.Writer = os.Stdout
	if outputFile != "" {
		file, err := cli.fs.Create(outputFile)
		if err != nil {
			return fmt.Errorf("failed to create output file: %w", err)
		}
		defer file.Close()
		output = file
	}

	// Encrypt the data
	encryptor, err := age.Encrypt(output, recipient)
	if err != nil {
		return fmt.Errorf("failed to create age encryptor: %w", err)
	}

	if _, err := io.Copy(encryptor, input); err != nil {
		return fmt.Errorf("failed to encrypt data: %w", err)
	}

	if err := encryptor.Close(); err != nil {
		return fmt.Errorf("failed to finalize encryption: %w", err)
	}

	return nil
}

// Decrypt decrypts data using an age secret key stored in a secret
func (cli *CLIInstance) Decrypt(secretName, inputFile, outputFile string) error {
	// Get current vault
	vault, err := secret.GetCurrentVault(cli.fs, cli.stateDir)
	if err != nil {
		return err
	}

	// Check if secret exists
	secretObj := secret.NewSecret(vault, secretName)
	exists, err := secretObj.Exists()
	if err != nil {
		return fmt.Errorf("failed to check if secret exists: %w", err)
	}

	if !exists {
		return fmt.Errorf("secret '%s' does not exist", secretName)
	}

	// Get the age secret key from the secret
	var secretValue []byte
	if os.Getenv(secret.EnvMnemonic) != "" {
		secretValue, err = secretObj.GetValue(nil)
	} else {
		unlockKey, unlockErr := vault.GetCurrentUnlockKey()
		if unlockErr != nil {
			return fmt.Errorf("failed to get current unlock key: %w", unlockErr)
		}
		secretValue, err = secretObj.GetValue(unlockKey)
	}
	if err != nil {
		return fmt.Errorf("failed to get secret value: %w", err)
	}

	ageSecretKey := string(secretValue)

	// Validate that it's a valid age secret key
	if !isValidAgeSecretKey(ageSecretKey) {
		return fmt.Errorf("secret '%s' does not contain a valid age secret key", secretName)
	}

	// Parse the age secret key to get the identity
	identity, err := age.ParseX25519Identity(ageSecretKey)
	if err != nil {
		return fmt.Errorf("failed to parse age secret key: %w", err)
	}

	// Set up input reader
	var input io.Reader = os.Stdin
	if inputFile != "" {
		file, err := cli.fs.Open(inputFile)
		if err != nil {
			return fmt.Errorf("failed to open input file: %w", err)
		}
		defer file.Close()
		input = file
	}

	// Set up output writer
	var output io.Writer = os.Stdout
	if outputFile != "" {
		file, err := cli.fs.Create(outputFile)
		if err != nil {
			return fmt.Errorf("failed to create output file: %w", err)
		}
		defer file.Close()
		output = file
	}

	// Decrypt the data
	decryptor, err := age.Decrypt(input, identity)
	if err != nil {
		return fmt.Errorf("failed to create age decryptor: %w", err)
	}

	if _, err := io.Copy(output, decryptor); err != nil {
		return fmt.Errorf("failed to decrypt data: %w", err)
	}

	return nil
}

// isValidAgeSecretKey checks if a string is a valid age secret key by attempting to parse it
func isValidAgeSecretKey(key string) bool {
	_, err := age.ParseX25519Identity(key)
	return err == nil
}