package cli

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"text/tabwriter"

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

// newVersionCmd returns the version management command
func newVersionCmd() *cobra.Command {
	cli := NewCLIInstance()
	return VersionCommands(cli)
}

// VersionCommands returns the version management commands
func VersionCommands(cli *CLIInstance) *cobra.Command {
	versionCmd := &cobra.Command{
		Use:   "version",
		Short: "Manage secret versions",
		Long:  "Commands for managing secret versions including listing, promoting, and retrieving specific versions",
	}

	// List versions command
	listCmd := &cobra.Command{
		Use:   "list <secret-name>",
		Short: "List all versions of a secret",
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			return cli.ListVersions(args[0])
		},
	}

	// Promote version command
	promoteCmd := &cobra.Command{
		Use:   "promote <secret-name> <version>",
		Short: "Promote a specific version to current",
		Long:  "Updates the current symlink to point to the specified version without modifying timestamps",
		Args:  cobra.ExactArgs(2),
		RunE: func(cmd *cobra.Command, args []string) error {
			return cli.PromoteVersion(args[0], args[1])
		},
	}

	versionCmd.AddCommand(listCmd, promoteCmd)
	return versionCmd
}

// ListVersions lists all versions of a secret
func (cli *CLIInstance) ListVersions(secretName string) error {
	secret.Debug("Listing versions for secret", "secret_name", secretName)

	// Get current vault
	vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
	if err != nil {
		return fmt.Errorf("failed to get current vault: %w", err)
	}

	// Get vault directory
	vaultDir, err := vlt.GetDirectory()
	if err != nil {
		return fmt.Errorf("failed to get vault directory: %w", err)
	}

	// Convert secret name to storage name
	storageName := strings.ReplaceAll(secretName, "/", "%")
	secretDir := filepath.Join(vaultDir, "secrets.d", storageName)

	// Check if secret exists
	exists, err := afero.DirExists(cli.fs, secretDir)
	if err != nil {
		return fmt.Errorf("failed to check if secret exists: %w", err)
	}
	if !exists {
		return fmt.Errorf("secret %s not found", secretName)
	}

	// Get all versions
	versions, err := secret.ListVersions(cli.fs, secretDir)
	if err != nil {
		return fmt.Errorf("failed to list versions: %w", err)
	}

	if len(versions) == 0 {
		fmt.Println("No versions found")
		return nil
	}

	// Get current version
	currentVersion, err := secret.GetCurrentVersion(cli.fs, secretDir)
	if err != nil {
		secret.Debug("Failed to get current version", "error", err)
		currentVersion = ""
	}

	// Get long-term key for decrypting metadata
	ltIdentity, err := vlt.GetOrDeriveLongTermKey()
	if err != nil {
		return fmt.Errorf("failed to get long-term key: %w", err)
	}

	// Create table writer
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
	fmt.Fprintln(w, "VERSION\tCREATED\tSTATUS\tNOT_BEFORE\tNOT_AFTER")

	// Load and display each version's metadata
	for _, version := range versions {
		sv := secret.NewSecretVersion(vlt, secretName, version)

		// Load metadata
		if err := sv.LoadMetadata(ltIdentity); err != nil {
			secret.Debug("Failed to load version metadata", "version", version, "error", err)
			// Display version with error
			status := "error"
			if version == currentVersion {
				status = "current (error)"
			}
			fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", version, "-", status, "-", "-")
			continue
		}

		// Determine status
		status := "expired"
		if version == currentVersion {
			status = "current"
		}

		// Format timestamps
		createdAt := "-"
		if sv.Metadata.CreatedAt != nil {
			createdAt = sv.Metadata.CreatedAt.Format("2006-01-02 15:04:05")
		}

		notBefore := "-"
		if sv.Metadata.NotBefore != nil {
			notBefore = sv.Metadata.NotBefore.Format("2006-01-02 15:04:05")
		}

		notAfter := "-"
		if sv.Metadata.NotAfter != nil {
			notAfter = sv.Metadata.NotAfter.Format("2006-01-02 15:04:05")
		}

		fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", version, createdAt, status, notBefore, notAfter)
	}

	w.Flush()
	return nil
}

// PromoteVersion promotes a specific version to current
func (cli *CLIInstance) PromoteVersion(secretName string, version string) error {
	secret.Debug("Promoting version", "secret_name", secretName, "version", version)

	// Get current vault
	vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
	if err != nil {
		return fmt.Errorf("failed to get current vault: %w", err)
	}

	// Get vault directory
	vaultDir, err := vlt.GetDirectory()
	if err != nil {
		return fmt.Errorf("failed to get vault directory: %w", err)
	}

	// Convert secret name to storage name
	storageName := strings.ReplaceAll(secretName, "/", "%")
	secretDir := filepath.Join(vaultDir, "secrets.d", storageName)

	// Check if secret exists
	exists, err := afero.DirExists(cli.fs, secretDir)
	if err != nil {
		return fmt.Errorf("failed to check if secret exists: %w", err)
	}
	if !exists {
		return fmt.Errorf("secret %s not found", secretName)
	}

	// Check if version exists
	versionPath := filepath.Join(secretDir, "versions", version)
	exists, err = afero.DirExists(cli.fs, versionPath)
	if err != nil {
		return fmt.Errorf("failed to check if version exists: %w", err)
	}
	if !exists {
		return fmt.Errorf("version %s not found for secret %s", version, secretName)
	}

	// Update current symlink
	if err := secret.SetCurrentVersion(cli.fs, secretDir, version); err != nil {
		return fmt.Errorf("failed to promote version: %w", err)
	}

	fmt.Printf("Promoted version %s to current for secret '%s'\n", version, secretName)
	return nil
}
