package main

import (
	"encoding/base64"
	"fmt"

	"hamlog/internal/applog"
	"hamlog/internal/secret"
)

// Secret-vault settings keys (NOT themselves sensitive — they hold the salt and
// an encrypted verifier token, never a password).
const (
	keySecretSalt     = "secret.salt"     // base64 PBKDF2 salt
	keySecretVerifier = "secret.verifier" // enc:v1: token, validates the passphrase
)

// sensitiveSettingKeys are the password fields encrypted at rest when the user
// sets a passphrase. Everything else stays plaintext.
var sensitiveSettingKeys = map[string]bool{
	keyQRZPassword:        true,
	keyHQPassword:         true,
	keyEmailPassword:      true,
	keyExtClublogPassword: true,
	keyExtLoTWKeyPassword: true,
	keyExtLoTWWebPassword: true,
	keyExtHRDLogCode:      true,
	keyExtHamqthPassword:  true,
	keyExtEQSLPassword:    true,
	keyExtCloudlogAPIKey:  true,
	// The web-publish config is one JSON blob and the FTP password lives inside
	// it, so the whole blob is encrypted. That costs nothing — it is read and
	// written as a unit anyway — and beats storing a server password in clear
	// next to the rest.
	keyWebPublish: true,
}

func isSensitiveSetting(key string) bool { return sensitiveSettingKeys[key] }

// SecretStatus tells the UI whether a passphrase is configured and unlocked.
type SecretStatus struct {
	HasPassphrase bool `json:"has_passphrase"`
	Unlocked      bool `json:"unlocked"`
}

// GetSecretStatus is polled at launch: if HasPassphrase && !Unlocked the UI
// shows the unlock prompt.
func (a *App) GetSecretStatus() SecretStatus {
	if a.settings == nil {
		return SecretStatus{}
	}
	salt, _ := a.settings.GetRaw(a.ctx, keySecretSalt)
	return SecretStatus{HasPassphrase: salt != "", Unlocked: a.settings.Unlocked()}
}

// reloadAfterSecretChange rebuilds anything that read passwords at startup while
// they were still locked (lookup providers + external-service config).
func (a *App) reloadAfterSecretChange() {
	a.reloadLookupProviders()
	if a.extsvc != nil {
		a.extsvc.SetConfig(a.loadExternalServices())
	}
}

// UnlockSecrets validates the passphrase against the stored verifier and, on
// success, installs the cipher so passwords decrypt for the rest of the session.
func (a *App) UnlockSecrets(passphrase string) error {
	if a.settings == nil {
		return fmt.Errorf("not initialized")
	}
	saltB64, _ := a.settings.GetRaw(a.ctx, keySecretSalt)
	verifier, _ := a.settings.GetRaw(a.ctx, keySecretVerifier)
	if saltB64 == "" || verifier == "" {
		return fmt.Errorf("no passphrase configured")
	}
	salt, err := base64.StdEncoding.DecodeString(saltB64)
	if err != nil {
		return fmt.Errorf("corrupt salt")
	}
	key, err := secret.DeriveKey(passphrase, salt)
	if err != nil {
		return fmt.Errorf("derive key: %w", err)
	}
	c, err := secret.New(key)
	if err != nil {
		return fmt.Errorf("cipher: %w", err)
	}
	if !c.CheckVerifier(verifier) {
		return fmt.Errorf("wrong passphrase")
	}
	a.settings.Unlock(c)
	a.reloadAfterSecretChange()
	return nil
}

// SetPassphrase sets a new passphrase or changes an existing one, then
// re-encrypts every stored secret under the new key. Changing an existing
// passphrase requires the vault to be unlocked first (so current secrets are
// readable as plaintext).
func (a *App) SetPassphrase(passphrase string) error {
	if a.settings == nil {
		return fmt.Errorf("not initialized")
	}
	if len(passphrase) < 4 {
		return fmt.Errorf("passphrase too short (min 4 characters)")
	}
	saltB64, _ := a.settings.GetRaw(a.ctx, keySecretSalt)
	if saltB64 != "" && !a.settings.Unlocked() {
		return fmt.Errorf("unlock with the current passphrase first")
	}
	// Snapshot current plaintext (Get decrypts when unlocked, passes through
	// when no passphrase yet).
	current := map[string]string{}
	for k := range sensitiveSettingKeys {
		if v, _ := a.settings.Get(a.ctx, k); v != "" {
			current[k] = v
		}
	}
	salt, err := secret.NewSalt()
	if err != nil {
		return fmt.Errorf("salt: %w", err)
	}
	key, err := secret.DeriveKey(passphrase, salt)
	if err != nil {
		return fmt.Errorf("derive key: %w", err)
	}
	c, err := secret.New(key)
	if err != nil {
		return fmt.Errorf("cipher: %w", err)
	}
	// Persist salt + verifier first so an interrupted migration is still
	// recoverable (the prompt appears next launch; plaintext secrets read
	// through transparently until re-encrypted).
	if err := a.settings.SetRaw(a.ctx, keySecretSalt, base64.StdEncoding.EncodeToString(salt)); err != nil {
		return err
	}
	if err := a.settings.SetRaw(a.ctx, keySecretVerifier, c.MakeVerifier()); err != nil {
		return err
	}
	a.settings.Unlock(c)
	for k, v := range current {
		if err := a.settings.Set(a.ctx, k, v); err != nil { // now encrypts
			applog.Printf("secret: re-encrypt %q failed: %v", k, err)
		}
	}
	a.reloadAfterSecretChange()
	return nil
}

// RemovePassphrase decrypts all secrets back to plaintext and clears the vault.
// Requires the current passphrase (validated) so it can't be cleared blindly.
func (a *App) RemovePassphrase(passphrase string) error {
	if a.settings == nil {
		return fmt.Errorf("not initialized")
	}
	if !a.settings.Unlocked() {
		if err := a.UnlockSecrets(passphrase); err != nil {
			return err
		}
	}
	// Read decrypted, then store back as plaintext and drop salt/verifier.
	plain := map[string]string{}
	for k := range sensitiveSettingKeys {
		if v, _ := a.settings.Get(a.ctx, k); v != "" {
			plain[k] = v
		}
	}
	a.settings.Lock()
	for k, v := range plain {
		if err := a.settings.SetRaw(a.ctx, k, v); err != nil {
			applog.Printf("secret: decrypt-on-remove %q failed: %v", k, err)
		}
	}
	_ = a.settings.SetRaw(a.ctx, keySecretSalt, "")
	_ = a.settings.SetRaw(a.ctx, keySecretVerifier, "")
	a.reloadAfterSecretChange()
	return nil
}
