package secrets

import (
	"bytes"
	"context"
	"database/sql"
	"errors"
	"os"
	"path/filepath"
	"testing"

	_ "modernc.org/sqlite"
)

func setupDB(t *testing.T) *sql.DB {
	t.Helper()
	dbPath := filepath.Join(t.TempDir(), "test.db")
	db, err := sql.Open("sqlite", dbPath)
	if err != nil {
		t.Fatalf("open sqlite: %v", err)
	}
	t.Cleanup(func() { _ = db.Close() })
	if _, err := db.Exec(`
		CREATE TABLE secrets (
			key             TEXT PRIMARY KEY,
			encrypted_value BLOB NOT NULL,
			updated_at      INTEGER NOT NULL,
			rotation_due_at INTEGER
		);`); err != nil {
		t.Fatalf("create table: %v", err)
	}
	return db
}

func setupStore(t *testing.T) *Store {
	t.Helper()
	db := setupDB(t)
	keyPath := filepath.Join(t.TempDir(), "age.key")
	store, err := New(db, Config{
		IdentityFile:   keyPath,
		CandidatePaths: []string{},
		AllowGenerate:  true,
	})
	if err != nil {
		t.Fatalf("new store: %v", err)
	}
	if store.KeyPath() == "" {
		t.Fatalf("KeyPath empty after generation")
	}
	if _, err := os.Stat(keyPath); err != nil {
		t.Fatalf("identity file not created at %s: %v", keyPath, err)
	}
	return store
}

func TestRoundTrip(t *testing.T) {
	store := setupStore(t)
	ctx := context.Background()

	original := []byte("the-tracker-cookie-value-which-must-stay-secret")
	if err := store.Set(ctx, "tracker.cookie", original); err != nil {
		t.Fatalf("Set: %v", err)
	}

	got, err := store.Get(ctx, "tracker.cookie")
	if err != nil {
		t.Fatalf("Get: %v", err)
	}
	if !bytes.Equal(got, original) {
		t.Fatalf("round-trip mismatch: got %q, want %q", got, original)
	}
}

func TestUpsertOverwrites(t *testing.T) {
	store := setupStore(t)
	ctx := context.Background()

	if err := store.Set(ctx, "k", []byte("v1")); err != nil {
		t.Fatalf("Set v1: %v", err)
	}
	if err := store.Set(ctx, "k", []byte("v2")); err != nil {
		t.Fatalf("Set v2: %v", err)
	}
	got, err := store.Get(ctx, "k")
	if err != nil {
		t.Fatalf("Get: %v", err)
	}
	if string(got) != "v2" {
		t.Fatalf("after upsert got %q, want v2", got)
	}
}

func TestDelete(t *testing.T) {
	store := setupStore(t)
	ctx := context.Background()

	if err := store.Set(ctx, "k", []byte("v")); err != nil {
		t.Fatalf("Set: %v", err)
	}
	if err := store.Delete(ctx, "k"); err != nil {
		t.Fatalf("Delete: %v", err)
	}
	if _, err := store.Get(ctx, "k"); !errors.Is(err, ErrNotFound) {
		t.Fatalf("Get after Delete: err = %v, want ErrNotFound", err)
	}
	if err := store.Delete(ctx, "k"); !errors.Is(err, ErrNotFound) {
		t.Fatalf("Delete again: err = %v, want ErrNotFound", err)
	}
}

func TestEncryptionIsOpaque(t *testing.T) {
	store := setupStore(t)
	ctx := context.Background()

	plaintext := []byte("plaintext-marker-7c4b9d")
	if err := store.Set(ctx, "k", plaintext); err != nil {
		t.Fatalf("Set: %v", err)
	}
	var blob []byte
	if err := store.db.QueryRow(`SELECT encrypted_value FROM secrets WHERE key = ?`, "k").Scan(&blob); err != nil {
		t.Fatalf("select blob: %v", err)
	}
	if bytes.Contains(blob, plaintext) {
		t.Fatalf("encrypted blob contains plaintext marker")
	}
}

func TestMissingIdentityWithoutGenerateFails(t *testing.T) {
	db := setupDB(t)
	_, err := New(db, Config{
		IdentityFile:   filepath.Join(t.TempDir(), "absent.key"),
		CandidatePaths: []string{},
		AllowGenerate:  false,
	})
	if err == nil {
		t.Fatalf("expected error when identity is missing and AllowGenerate is false")
	}
}

func TestReuseExistingIdentity(t *testing.T) {
	db := setupDB(t)
	keyPath := filepath.Join(t.TempDir(), "age.key")

	// First creation generates and persists.
	s1, err := New(db, Config{IdentityFile: keyPath, AllowGenerate: true})
	if err != nil {
		t.Fatalf("first New: %v", err)
	}
	ctx := context.Background()
	if err := s1.Set(ctx, "k", []byte("pre")); err != nil {
		t.Fatalf("Set: %v", err)
	}

	// Second instance must load the same identity and decrypt the previous value.
	s2, err := New(db, Config{IdentityFile: keyPath, AllowGenerate: false})
	if err != nil {
		t.Fatalf("second New: %v", err)
	}
	got, err := s2.Get(ctx, "k")
	if err != nil {
		t.Fatalf("Get from second store: %v", err)
	}
	if string(got) != "pre" {
		t.Fatalf("got %q, want pre", got)
	}
}
