package secret

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

	"filippo.io/age"
	"github.com/oklog/ulid/v2"
	"github.com/spf13/afero"
)

// VersionMetadata contains information about a secret version
type VersionMetadata struct {
	ID        string     `json:"id"`                  // ULID
	CreatedAt *time.Time `json:"createdAt,omitempty"` // When version was created
	NotBefore *time.Time `json:"notBefore,omitempty"` // When this version becomes active
	NotAfter  *time.Time `json:"notAfter,omitempty"`  // When this version expires (nil = current)
}

// SecretVersion represents a version of a secret
type SecretVersion struct {
	SecretName string
	Version    string
	Directory  string
	Metadata   VersionMetadata
	vault      VaultInterface
}

// NewSecretVersion creates a new SecretVersion instance
func NewSecretVersion(vault VaultInterface, secretName string, version string) *SecretVersion {
	DebugWith("Creating new secret version instance",
		slog.String("secret_name", secretName),
		slog.String("version", version),
		slog.String("vault_name", vault.GetName()),
	)

	vaultDir, _ := vault.GetDirectory()
	storageName := strings.ReplaceAll(secretName, "/", "%")
	versionDir := filepath.Join(vaultDir, "secrets.d", storageName, "versions", version)

	DebugWith("Secret version storage details",
		slog.String("secret_name", secretName),
		slog.String("version", version),
		slog.String("version_dir", versionDir),
	)

	now := time.Now()
	return &SecretVersion{
		SecretName: secretName,
		Version:    version,
		Directory:  versionDir,
		vault:      vault,
		Metadata: VersionMetadata{
			ID:        ulid.Make().String(),
			CreatedAt: &now,
		},
	}
}

// GenerateVersionName generates a new version name in YYYYMMDD.NNN format
func GenerateVersionName(fs afero.Fs, secretDir string) (string, error) {
	today := time.Now().Format("20060102")
	versionsDir := filepath.Join(secretDir, "versions")

	// Ensure versions directory exists
	if err := fs.MkdirAll(versionsDir, DirPerms); err != nil {
		return "", fmt.Errorf("failed to create versions directory: %w", err)
	}

	// Find the highest serial number for today
	entries, err := afero.ReadDir(fs, versionsDir)
	if err != nil {
		return "", fmt.Errorf("failed to read versions directory: %w", err)
	}

	maxSerial := 0
	prefix := today + "."

	for _, entry := range entries {
		if entry.IsDir() && strings.HasPrefix(entry.Name(), prefix) {
			// Extract serial number
			parts := strings.Split(entry.Name(), ".")
			if len(parts) == 2 {
				var serial int
				if _, err := fmt.Sscanf(parts[1], "%03d", &serial); err == nil {
					if serial > maxSerial {
						maxSerial = serial
					}
				}
			}
		}
	}

	// Generate new version name
	newSerial := maxSerial + 1
	if newSerial > 999 {
		return "", fmt.Errorf("exceeded maximum versions per day (999)")
	}

	return fmt.Sprintf("%s.%03d", today, newSerial), nil
}

// Save saves the version metadata and value
func (sv *SecretVersion) Save(value []byte) error {
	DebugWith("Saving secret version",
		slog.String("secret_name", sv.SecretName),
		slog.String("version", sv.Version),
		slog.Int("value_length", len(value)),
	)

	fs := sv.vault.GetFilesystem()

	// Create version directory
	if err := fs.MkdirAll(sv.Directory, DirPerms); err != nil {
		Debug("Failed to create version directory", "error", err, "dir", sv.Directory)
		return fmt.Errorf("failed to create version directory: %w", err)
	}

	// Step 1: Generate a new keypair for this version
	Debug("Generating version-specific keypair", "version", sv.Version)
	versionIdentity, err := age.GenerateX25519Identity()
	if err != nil {
		Debug("Failed to generate version keypair", "error", err, "version", sv.Version)
		return fmt.Errorf("failed to generate version keypair: %w", err)
	}

	versionPublicKey := versionIdentity.Recipient().String()
	versionPrivateKey := versionIdentity.String()

	DebugWith("Generated version keypair",
		slog.String("version", sv.Version),
		slog.String("public_key", versionPublicKey),
	)

	// Step 2: Store the version's public key
	pubKeyPath := filepath.Join(sv.Directory, "pub.age")
	Debug("Writing version public key", "path", pubKeyPath)
	if err := afero.WriteFile(fs, pubKeyPath, []byte(versionPublicKey), FilePerms); err != nil {
		Debug("Failed to write version public key", "error", err, "path", pubKeyPath)
		return fmt.Errorf("failed to write version public key: %w", err)
	}

	// Step 3: Encrypt the value to the version's public key
	Debug("Encrypting value to version's public key", "version", sv.Version)
	encryptedValue, err := EncryptToRecipient(value, versionIdentity.Recipient())
	if err != nil {
		Debug("Failed to encrypt version value", "error", err, "version", sv.Version)
		return fmt.Errorf("failed to encrypt version value: %w", err)
	}

	// Step 4: Store the encrypted value
	valuePath := filepath.Join(sv.Directory, "value.age")
	Debug("Writing encrypted version value", "path", valuePath)
	if err := afero.WriteFile(fs, valuePath, encryptedValue, FilePerms); err != nil {
		Debug("Failed to write encrypted version value", "error", err, "path", valuePath)
		return fmt.Errorf("failed to write encrypted version value: %w", err)
	}

	// Step 5: Get vault's long-term public key for encrypting the version's private key
	vaultDir, _ := sv.vault.GetDirectory()
	ltPubKeyPath := filepath.Join(vaultDir, "pub.age")
	Debug("Reading long-term public key", "path", ltPubKeyPath)

	ltPubKeyData, err := afero.ReadFile(fs, ltPubKeyPath)
	if err != nil {
		Debug("Failed to read long-term public key", "error", err, "path", ltPubKeyPath)
		return fmt.Errorf("failed to read long-term public key: %w", err)
	}

	Debug("Parsing long-term public key")
	ltRecipient, err := age.ParseX25519Recipient(string(ltPubKeyData))
	if err != nil {
		Debug("Failed to parse long-term public key", "error", err)
		return fmt.Errorf("failed to parse long-term public key: %w", err)
	}

	// Step 6: Encrypt the version's private key to the long-term public key
	Debug("Encrypting version private key to long-term public key", "version", sv.Version)
	encryptedPrivKey, err := EncryptToRecipient([]byte(versionPrivateKey), ltRecipient)
	if err != nil {
		Debug("Failed to encrypt version private key", "error", err, "version", sv.Version)
		return fmt.Errorf("failed to encrypt version private key: %w", err)
	}

	// Step 7: Store the encrypted private key
	privKeyPath := filepath.Join(sv.Directory, "priv.age")
	Debug("Writing encrypted version private key", "path", privKeyPath)
	if err := afero.WriteFile(fs, privKeyPath, encryptedPrivKey, FilePerms); err != nil {
		Debug("Failed to write encrypted version private key", "error", err, "path", privKeyPath)
		return fmt.Errorf("failed to write encrypted version private key: %w", err)
	}

	// Step 8: Encrypt and store metadata
	Debug("Encrypting version metadata", "version", sv.Version)
	metadataBytes, err := json.MarshalIndent(sv.Metadata, "", "  ")
	if err != nil {
		Debug("Failed to marshal version metadata", "error", err)
		return fmt.Errorf("failed to marshal version metadata: %w", err)
	}

	// Encrypt metadata to the version's public key
	encryptedMetadata, err := EncryptToRecipient(metadataBytes, versionIdentity.Recipient())
	if err != nil {
		Debug("Failed to encrypt version metadata", "error", err, "version", sv.Version)
		return fmt.Errorf("failed to encrypt version metadata: %w", err)
	}

	metadataPath := filepath.Join(sv.Directory, "metadata.age")
	Debug("Writing encrypted version metadata", "path", metadataPath)
	if err := afero.WriteFile(fs, metadataPath, encryptedMetadata, FilePerms); err != nil {
		Debug("Failed to write encrypted version metadata", "error", err, "path", metadataPath)
		return fmt.Errorf("failed to write encrypted version metadata: %w", err)
	}

	Debug("Successfully saved secret version", "version", sv.Version, "secret_name", sv.SecretName)
	return nil
}

// LoadMetadata loads and decrypts the version metadata
func (sv *SecretVersion) LoadMetadata(ltIdentity *age.X25519Identity) error {
	DebugWith("Loading version metadata",
		slog.String("secret_name", sv.SecretName),
		slog.String("version", sv.Version),
	)

	fs := sv.vault.GetFilesystem()

	// Step 1: Read encrypted version private key
	encryptedPrivKeyPath := filepath.Join(sv.Directory, "priv.age")
	encryptedPrivKey, err := afero.ReadFile(fs, encryptedPrivKeyPath)
	if err != nil {
		Debug("Failed to read encrypted version private key", "error", err, "path", encryptedPrivKeyPath)
		return fmt.Errorf("failed to read encrypted version private key: %w", err)
	}

	// Step 2: Decrypt version private key using long-term key
	versionPrivKeyData, err := DecryptWithIdentity(encryptedPrivKey, ltIdentity)
	if err != nil {
		Debug("Failed to decrypt version private key", "error", err, "version", sv.Version)
		return fmt.Errorf("failed to decrypt version private key: %w", err)
	}

	// Step 3: Parse version private key
	versionIdentity, err := age.ParseX25519Identity(string(versionPrivKeyData))
	if err != nil {
		Debug("Failed to parse version private key", "error", err, "version", sv.Version)
		return fmt.Errorf("failed to parse version private key: %w", err)
	}

	// Step 4: Read encrypted metadata
	encryptedMetadataPath := filepath.Join(sv.Directory, "metadata.age")
	encryptedMetadata, err := afero.ReadFile(fs, encryptedMetadataPath)
	if err != nil {
		Debug("Failed to read encrypted version metadata", "error", err, "path", encryptedMetadataPath)
		return fmt.Errorf("failed to read encrypted version metadata: %w", err)
	}

	// Step 5: Decrypt metadata using version key
	metadataBytes, err := DecryptWithIdentity(encryptedMetadata, versionIdentity)
	if err != nil {
		Debug("Failed to decrypt version metadata", "error", err, "version", sv.Version)
		return fmt.Errorf("failed to decrypt version metadata: %w", err)
	}

	// Step 6: Unmarshal metadata
	var metadata VersionMetadata
	if err := json.Unmarshal(metadataBytes, &metadata); err != nil {
		Debug("Failed to unmarshal version metadata", "error", err, "version", sv.Version)
		return fmt.Errorf("failed to unmarshal version metadata: %w", err)
	}

	sv.Metadata = metadata
	Debug("Successfully loaded version metadata", "version", sv.Version)
	return nil
}

// GetValue retrieves and decrypts the version value
func (sv *SecretVersion) GetValue(ltIdentity *age.X25519Identity) ([]byte, error) {
	DebugWith("Getting version value",
		slog.String("secret_name", sv.SecretName),
		slog.String("version", sv.Version),
	)

	fs := sv.vault.GetFilesystem()

	// Step 1: Read encrypted version private key
	encryptedPrivKeyPath := filepath.Join(sv.Directory, "priv.age")
	encryptedPrivKey, err := afero.ReadFile(fs, encryptedPrivKeyPath)
	if err != nil {
		Debug("Failed to read encrypted version private key", "error", err, "path", encryptedPrivKeyPath)
		return nil, fmt.Errorf("failed to read encrypted version private key: %w", err)
	}

	// Step 2: Decrypt version private key using long-term key
	versionPrivKeyData, err := DecryptWithIdentity(encryptedPrivKey, ltIdentity)
	if err != nil {
		Debug("Failed to decrypt version private key", "error", err, "version", sv.Version)
		return nil, fmt.Errorf("failed to decrypt version private key: %w", err)
	}

	// Step 3: Parse version private key
	versionIdentity, err := age.ParseX25519Identity(string(versionPrivKeyData))
	if err != nil {
		Debug("Failed to parse version private key", "error", err, "version", sv.Version)
		return nil, fmt.Errorf("failed to parse version private key: %w", err)
	}

	// Step 4: Read encrypted value
	encryptedValuePath := filepath.Join(sv.Directory, "value.age")
	encryptedValue, err := afero.ReadFile(fs, encryptedValuePath)
	if err != nil {
		Debug("Failed to read encrypted version value", "error", err, "path", encryptedValuePath)
		return nil, fmt.Errorf("failed to read encrypted version value: %w", err)
	}

	// Step 5: Decrypt value using version key
	value, err := DecryptWithIdentity(encryptedValue, versionIdentity)
	if err != nil {
		Debug("Failed to decrypt version value", "error", err, "version", sv.Version)
		return nil, fmt.Errorf("failed to decrypt version value: %w", err)
	}

	Debug("Successfully retrieved version value", "version", sv.Version, "value_length", len(value))
	return value, nil
}

// ListVersions lists all versions of a secret
func ListVersions(fs afero.Fs, secretDir string) ([]string, error) {
	versionsDir := filepath.Join(secretDir, "versions")

	// Check if versions directory exists
	exists, err := afero.DirExists(fs, versionsDir)
	if err != nil {
		return nil, fmt.Errorf("failed to check versions directory: %w", err)
	}
	if !exists {
		return []string{}, nil
	}

	// List all version directories
	entries, err := afero.ReadDir(fs, versionsDir)
	if err != nil {
		return nil, fmt.Errorf("failed to read versions directory: %w", err)
	}

	var versions []string
	for _, entry := range entries {
		if entry.IsDir() {
			versions = append(versions, entry.Name())
		}
	}

	// Sort versions in reverse chronological order
	sort.Sort(sort.Reverse(sort.StringSlice(versions)))

	return versions, nil
}

// GetCurrentVersion returns the version that the "current" symlink points to
func GetCurrentVersion(fs afero.Fs, secretDir string) (string, error) {
	currentPath := filepath.Join(secretDir, "current")

	// Try to read as a real symlink first
	if _, ok := fs.(*afero.OsFs); ok {
		target, err := os.Readlink(currentPath)
		if err == nil {
			// Extract version from path (e.g., "versions/20231215.001" -> "20231215.001")
			parts := strings.Split(target, "/")
			if len(parts) >= 2 && parts[0] == "versions" {
				return parts[1], nil
			}
			return "", fmt.Errorf("invalid current version symlink format: %s", target)
		}
	}

	// Fall back to reading as a file (for MemMapFs testing)
	fileData, err := afero.ReadFile(fs, currentPath)
	if err != nil {
		return "", fmt.Errorf("failed to read current version symlink: %w", err)
	}

	target := strings.TrimSpace(string(fileData))

	// Extract version from path
	parts := strings.Split(target, "/")
	if len(parts) >= 2 && parts[0] == "versions" {
		return parts[1], nil
	}

	return "", fmt.Errorf("invalid current version symlink format: %s", target)
}

// SetCurrentVersion updates the "current" symlink to point to a specific version
func SetCurrentVersion(fs afero.Fs, secretDir string, version string) error {
	currentPath := filepath.Join(secretDir, "current")
	targetPath := filepath.Join("versions", version)

	// Remove existing symlink if it exists
	_ = fs.Remove(currentPath)

	// Try to create a real symlink first (works on Unix systems)
	if _, ok := fs.(*afero.OsFs); ok {
		if err := os.Symlink(targetPath, currentPath); err == nil {
			return nil
		}
	}

	// Fall back to creating a file with the target path (for MemMapFs testing)
	if err := afero.WriteFile(fs, currentPath, []byte(targetPath), FilePerms); err != nil {
		return fmt.Errorf("failed to create current version symlink: %w", err)
	}

	return nil
}
