package main

import (
	"fmt"
	"os"

	"git.wntrmute.dev/mc/mcias/internal/crypto"
	"git.wntrmute.dev/mc/mcias/internal/db"
)

// runRekey re-encrypts all secrets under a new passphrase-derived master key.
//
// The current master key (already loaded in tool.masterKey by openDB) is used
// to decrypt every encrypted secret: the Ed25519 signing key, all TOTP secrets,
// and all Postgres credential passwords.  The operator is then prompted for a
// new passphrase (confirmed), a fresh Argon2id salt is generated, a new 256-bit
// master key is derived, and all secrets are re-encrypted and written back in a
// single atomic SQLite transaction.
//
// Security: The entire re-encryption happens in memory first; the database is
// only updated once all ciphertext has been produced successfully.  The new
// salt replaces the old salt atomically within the same transaction so the
// database is never left in a mixed state.  Both the old and new master keys
// are zeroed in deferred cleanup.  No secret material is logged or printed.
func (t *tool) runRekey(_ []string) {
	// ── 1. Decrypt signing key under old master key ──────────────────────
	sigKeyEnc, sigKeyNonce, err := t.db.ReadServerConfig()
	if err != nil {
		fatalf("read server config: %v", err)
	}
	sigKeyPEM, err := crypto.OpenAESGCM(t.masterKey, sigKeyNonce, sigKeyEnc)
	if err != nil {
		fatalf("decrypt signing key: %v", err)
	}

	// ── 2. Decrypt all TOTP secrets under old master key ─────────────────
	totpAccounts, err := t.db.ListAccountsWithTOTP()
	if err != nil {
		fatalf("list accounts with TOTP: %v", err)
	}
	type totpPlain struct {
		secret    []byte
		accountID int64
	}
	totpPlaintexts := make([]totpPlain, 0, len(totpAccounts))
	for _, a := range totpAccounts {
		pt, err := crypto.OpenAESGCM(t.masterKey, a.TOTPSecretNonce, a.TOTPSecretEnc)
		if err != nil {
			fatalf("decrypt TOTP secret for account %s: %v", a.Username, err)
		}
		totpPlaintexts = append(totpPlaintexts, totpPlain{accountID: a.ID, secret: pt})
	}

	// ── 3. Decrypt all pg_credentials passwords under old master key ──────
	pgCreds, err := t.db.ListAllPGCredentials()
	if err != nil {
		fatalf("list pg credentials: %v", err)
	}
	type pgPlain struct {
		password []byte
		credID   int64
	}
	pgPlaintexts := make([]pgPlain, 0, len(pgCreds))
	for _, c := range pgCreds {
		pt, err := crypto.OpenAESGCM(t.masterKey, c.PGPasswordNonce, c.PGPasswordEnc)
		if err != nil {
			fatalf("decrypt pg password for credential %d: %v", c.ID, err)
		}
		pgPlaintexts = append(pgPlaintexts, pgPlain{credID: c.ID, password: pt})
	}

	// ── 4. Prompt for new passphrase (confirmed) ──────────────────────────
	fmt.Fprintln(os.Stderr, "Enter new master passphrase (will not echo):")
	newPassphrase, err := readPassword("New passphrase: ")
	if err != nil {
		fatalf("read passphrase: %v", err)
	}
	if newPassphrase == "" {
		fatalf("passphrase must not be empty")
	}
	confirm, err := readPassword("Confirm passphrase: ")
	if err != nil {
		fatalf("read passphrase confirmation: %v", err)
	}
	if newPassphrase != confirm {
		fatalf("passphrases do not match")
	}

	// ── 5. Derive new master key ──────────────────────────────────────────
	// Security: a fresh random salt is generated for every rekey so that the
	// new key is independent of the old key even if the same passphrase is
	// reused.  The new salt is stored atomically with the re-encrypted secrets.
	newSalt, err := crypto.NewSalt()
	if err != nil {
		fatalf("generate new salt: %v", err)
	}
	newKey, err := crypto.DeriveKey(newPassphrase, newSalt)
	if err != nil {
		fatalf("derive new master key: %v", err)
	}
	// Zero both keys when done, regardless of outcome.
	defer func() {
		for i := range newKey {
			newKey[i] = 0
		}
	}()

	// ── 6. Re-encrypt signing key ─────────────────────────────────────────
	newSigKeyEnc, newSigKeyNonce, err := crypto.SealAESGCM(newKey, sigKeyPEM)
	if err != nil {
		fatalf("re-encrypt signing key: %v", err)
	}

	// ── 7. Re-encrypt TOTP secrets ────────────────────────────────────────
	totpRows := make([]db.TOTPRekeyRow, 0, len(totpPlaintexts))
	for _, tp := range totpPlaintexts {
		enc, nonce, err := crypto.SealAESGCM(newKey, tp.secret)
		if err != nil {
			fatalf("re-encrypt TOTP secret for account %d: %v", tp.accountID, err)
		}
		totpRows = append(totpRows, db.TOTPRekeyRow{
			AccountID: tp.accountID,
			Enc:       enc,
			Nonce:     nonce,
		})
	}

	// ── 8. Re-encrypt pg_credentials passwords ────────────────────────────
	pgRows := make([]db.PGRekeyRow, 0, len(pgPlaintexts))
	for _, pp := range pgPlaintexts {
		enc, nonce, err := crypto.SealAESGCM(newKey, pp.password)
		if err != nil {
			fatalf("re-encrypt pg password for credential %d: %v", pp.credID, err)
		}
		pgRows = append(pgRows, db.PGRekeyRow{
			CredentialID: pp.credID,
			Enc:          enc,
			Nonce:        nonce,
		})
	}

	// ── 9. Atomic commit ──────────────────────────────────────────────────
	if err := t.db.Rekey(newSalt, newSigKeyEnc, newSigKeyNonce, totpRows, pgRows); err != nil {
		fatalf("rekey database: %v", err)
	}

	if err := t.db.WriteAuditEvent("master_key_rekeyed", nil, nil, "", `{"actor":"mciasdb"}`); err != nil {
		fmt.Fprintf(os.Stderr, "warning: write audit event: %v\n", err)
	}

	fmt.Printf("Rekey complete: %d TOTP secrets and %d pg credentials re-encrypted.\n",
		len(totpRows), len(pgRows))
	fmt.Fprintln(os.Stderr, "Update your mcias.toml or passphrase environment variable to use the new passphrase.")
}
