package store

import (
	"context"
	"path/filepath"
	"testing"
)

func openAuditTestDB(t *testing.T) (*AuditRepo, func()) {
	t.Helper()
	path := filepath.Join(t.TempDir(), "audit.db")
	db, err := Open(path)
	if err != nil {
		t.Fatalf("open db: %v", err)
	}
	return NewAuditRepo(db), func() { _ = db.Close() }
}

func TestAuditRepo_AppendAndList(t *testing.T) {
	repo, cleanup := openAuditTestDB(t)
	defer cleanup()

	ctx := context.Background()
	for i := 0; i < 5; i++ {
		err := repo.Append(ctx, &AuditEntry{
			Actor:    "cli",
			Action:   "node.join",
			Resource: "node-1",
			Result:   "success",
		})
		if err != nil {
			t.Fatalf("append[%d]: %v", i, err)
		}
	}
	entries, err := repo.List(ctx, 10)
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	if len(entries) != 5 {
		t.Errorf("expected 5 entries, got %d", len(entries))
	}
}

func TestAuditRepo_WithError(t *testing.T) {
	repo, cleanup := openAuditTestDB(t)
	defer cleanup()

	ctx := context.Background()
	err := repo.Append(ctx, &AuditEntry{
		Actor:    "system",
		Action:   "task.run",
		Resource: "task-1",
		Result:   "failure",
		Error:    "exit status 1",
		Metadata: map[string]any{"exit_code": 1},
	})
	if err != nil {
		t.Fatalf("append: %v", err)
	}
	entries, _ := repo.List(ctx, 1)
	if len(entries) != 1 {
		t.Fatalf("expected 1 entry, got %d", len(entries))
	}
	if entries[0].Error != "exit status 1" {
		t.Errorf("expected error 'exit status 1', got %q", entries[0].Error)
	}
}

func TestAuditRepo_MetadataRoundTrip(t *testing.T) {
	repo, cleanup := openAuditTestDB(t)
	defer cleanup()

	ctx := context.Background()
	want := map[string]any{"node": "node-1", "exit_code": float64(2)}
	if err := repo.Append(ctx, &AuditEntry{
		Actor:    "cli",
		Action:   "node.join",
		Resource: "node-1",
		Result:   "success",
		Metadata: want,
	}); err != nil {
		t.Fatalf("append: %v", err)
	}
	entries, err := repo.List(ctx, 10)
	if err != nil {
		t.Fatalf("List: %v", err)
	}
	if len(entries) != 1 {
		t.Fatalf("expected 1 entry, got %d", len(entries))
	}
	if entries[0].Metadata == nil {
		t.Fatalf("metadata not round-tripped")
	}
	if entries[0].Metadata["node"] != "node-1" {
		t.Errorf("metadata[node] = %v, want node-1", entries[0].Metadata["node"])
	}
}

func TestAuditRepo_DefaultActorAndTimestamp(t *testing.T) {
	repo, cleanup := openAuditTestDB(t)
	defer cleanup()

	ctx := context.Background()
	// Append with empty Actor and zero Timestamp — defaults should apply.
	if err := repo.Append(ctx, &AuditEntry{
		Action:   "x",
		Resource: "y",
		Result:   "success",
	}); err != nil {
		t.Fatalf("append: %v", err)
	}
	entries, _ := repo.List(ctx, 1)
	if len(entries) != 1 {
		t.Fatalf("expected 1 entry, got %d", len(entries))
	}
	if entries[0].Actor != "system" {
		t.Errorf("default actor = %q, want system", entries[0].Actor)
	}
	if entries[0].Timestamp.IsZero() {
		t.Errorf("default timestamp not set")
	}
}

func TestAuditRepo_ListDefaultLimit(t *testing.T) {
	repo, cleanup := openAuditTestDB(t)
	defer cleanup()

	ctx := context.Background()
	for i := 0; i < 5; i++ {
		if err := repo.Append(ctx, &AuditEntry{
			Action: "x", Resource: "y", Result: "success",
		}); err != nil {
			t.Fatalf("append[%d]: %v", i, err)
		}
	}
	// limit<=0 should default to 100.
	entries, err := repo.List(ctx, 0)
	if err != nil {
		t.Fatalf("List(0): %v", err)
	}
	if len(entries) != 5 {
		t.Errorf("List(0): got %d, want 5", len(entries))
	}
	entries, err = repo.List(ctx, -1)
	if err != nil {
		t.Fatalf("List(-1): %v", err)
	}
	if len(entries) != 5 {
		t.Errorf("List(-1): got %d, want 5", len(entries))
	}
}

// --- REQ-125 / F2 audit tamper-evidence tests ---

// TestAuditRepo_VerifyChain verifies the hash chain verifies after append.
func TestAuditRepo_VerifyChain(t *testing.T) {
	repo, cleanup := openAuditTestDB(t)
	defer cleanup()
	ctx := context.Background()
	for i := 0; i < 5; i++ {
		if err := repo.Append(ctx, &AuditEntry{
			Action:   "test.action",
			Resource: "res",
			Result:   "success",
			Actor:    "user",
		}); err != nil {
			t.Fatalf("Append %d: %v", i, err)
		}
	}
	if err := repo.VerifyChain(ctx); err != nil {
		t.Errorf("VerifyChain: %v", err)
	}
}

// TestAuditRepo_TamperDetection verifies VerifyChain detects a modified
// entry. We use raw SQL to UPDATE (which the trigger should block).
func TestAuditRepo_TamperDetection(t *testing.T) {
	repo, cleanup := openAuditTestDB(t)
	defer cleanup()
	ctx := context.Background()
	if err := repo.Append(ctx, &AuditEntry{
		Action: "cert.issued", Resource: "node1", Result: "success", Actor: "system",
	}); err != nil {
		t.Fatalf("Append: %v", err)
	}
	// Verify chain is intact.
	if err := repo.VerifyChain(ctx); err != nil {
		t.Fatalf("VerifyChain before tamper: %v", err)
	}
	// Attempt UPDATE — the trigger should block it.
	_, err := repo.db.ExecContext(ctx, `UPDATE audit_log SET actor='hacker' WHERE id=1`)
	if err == nil {
		t.Error("UPDATE should be blocked by append-only trigger (REQ-125)")
	}
	// Attempt DELETE — also blocked.
	_, err = repo.db.ExecContext(ctx, `DELETE FROM audit_log WHERE id=1`)
	if err == nil {
		t.Error("DELETE should be blocked by append-only trigger (REQ-125)")
	}
	// Chain still verifies (nothing was modified).
	if err := repo.VerifyChain(ctx); err != nil {
		t.Errorf("VerifyChain after blocked tamper: %v", err)
	}
}
