package secret

import (
	"fmt"
	"log/slog"
	"os"
	"path/filepath"

	"filippo.io/age"
	"github.com/spf13/afero"
)

// PassphraseUnlocker represents a passphrase-protected unlocker
type PassphraseUnlocker struct {
	Directory  string
	Metadata   UnlockerMetadata
	fs         afero.Fs
	Passphrase string
}

// GetIdentity implements Unlocker interface for passphrase-based unlockers
func (p *PassphraseUnlocker) GetIdentity() (*age.X25519Identity, error) {
	DebugWith("Getting passphrase unlocker identity",
		slog.String("unlocker_id", p.GetID()),
		slog.String("unlocker_type", p.GetType()),
	)

	// First check if we already have the passphrase
	passphraseStr := p.Passphrase
	if passphraseStr == "" {
		Debug("No passphrase in memory, checking environment")
		// Check environment variable for passphrase
		passphraseStr = os.Getenv(EnvUnlockPassphrase)
		if passphraseStr == "" {
			Debug("No passphrase in environment, prompting user")
			// Prompt for passphrase
			var err error
			passphraseStr, err = ReadPassphrase("Enter unlock passphrase: ")
			if err != nil {
				Debug("Failed to read passphrase", "error", err, "unlocker_id", p.GetID())
				return nil, fmt.Errorf("failed to read passphrase: %w", err)
			}
		} else {
			Debug("Using passphrase from environment", "unlocker_id", p.GetID())
		}
	} else {
		Debug("Using in-memory passphrase", "unlocker_id", p.GetID())
	}

	// Read encrypted private key of unlocker
	unlockerPrivPath := filepath.Join(p.Directory, "priv.age")
	Debug("Reading encrypted passphrase unlocker", "path", unlockerPrivPath)

	encryptedPrivKeyData, err := afero.ReadFile(p.fs, unlockerPrivPath)
	if err != nil {
		Debug("Failed to read passphrase unlocker private key", "error", err, "path", unlockerPrivPath)
		return nil, fmt.Errorf("failed to read unlocker private key: %w", err)
	}

	DebugWith("Read encrypted passphrase unlocker",
		slog.String("unlocker_id", p.GetID()),
		slog.Int("encrypted_length", len(encryptedPrivKeyData)),
	)

	Debug("Decrypting unlocker private key with passphrase", "unlocker_id", p.GetID())

	// Decrypt the unlocker private key with passphrase
	privKeyData, err := DecryptWithPassphrase(encryptedPrivKeyData, passphraseStr)
	if err != nil {
		Debug("Failed to decrypt unlocker private key", "error", err, "unlocker_id", p.GetID())
		return nil, fmt.Errorf("failed to decrypt unlocker private key: %w", err)
	}

	DebugWith("Successfully decrypted unlocker private key",
		slog.String("unlocker_id", p.GetID()),
		slog.Int("decrypted_length", len(privKeyData)),
	)

	// Parse the decrypted private key
	Debug("Parsing decrypted unlocker identity", "unlocker_id", p.GetID())
	identity, err := age.ParseX25519Identity(string(privKeyData))
	if err != nil {
		Debug("Failed to parse unlocker private key", "error", err, "unlocker_id", p.GetID())
		return nil, fmt.Errorf("failed to parse unlocker private key: %w", err)
	}

	DebugWith("Successfully parsed passphrase unlocker identity",
		slog.String("unlocker_id", p.GetID()),
		slog.String("public_key", identity.Recipient().String()),
	)

	return identity, nil
}

// GetType implements Unlocker interface
func (p *PassphraseUnlocker) GetType() string {
	return "passphrase"
}

// GetMetadata implements Unlocker interface
func (p *PassphraseUnlocker) GetMetadata() UnlockerMetadata {
	return p.Metadata
}

// GetDirectory implements Unlocker interface
func (p *PassphraseUnlocker) GetDirectory() string {
	return p.Directory
}

// GetID implements Unlocker interface
func (p *PassphraseUnlocker) GetID() string {
	return p.Metadata.ID
}

// ID implements Unlocker interface - generates ID from creation timestamp
func (p *PassphraseUnlocker) ID() string {
	// Generate ID using creation timestamp: YYYY-MM-DD.HH.mm-passphrase
	createdAt := p.Metadata.CreatedAt
	return fmt.Sprintf("%s-passphrase", createdAt.Format("2006-01-02.15.04"))
}

// Remove implements Unlocker interface - removes the passphrase unlocker
func (p *PassphraseUnlocker) Remove() error {
	// For passphrase unlockers, we just need to remove the directory
	// No external resources (like keychain items) to clean up
	if err := p.fs.RemoveAll(p.Directory); err != nil {
		return fmt.Errorf("failed to remove passphrase unlocker directory: %w", err)
	}
	return nil
}

// NewPassphraseUnlocker creates a new PassphraseUnlocker instance
func NewPassphraseUnlocker(fs afero.Fs, directory string, metadata UnlockerMetadata) *PassphraseUnlocker {
	return &PassphraseUnlocker{
		Directory: directory,
		Metadata:  metadata,
		fs:        fs,
	}
}

// CreatePassphraseUnlocker creates a new passphrase-protected unlocker
func CreatePassphraseUnlocker(fs afero.Fs, stateDir string, passphrase string) (*PassphraseUnlocker, error) {
	// Get current vault
	currentVault, err := GetCurrentVault(fs, stateDir)
	if err != nil {
		return nil, fmt.Errorf("failed to get current vault: %w", err)
	}

	return currentVault.CreatePassphraseUnlocker(passphrase)
}
