package audit

import (
	"bytes"
	"context"
	"errors"
	"log/slog"
	"path/filepath"
	"strings"
	"testing"

	"git.cloudinit.dev/coreci/orca/internal/engine"
	"git.cloudinit.dev/coreci/orca/internal/store"
)

func newTestAudit(t *testing.T) (*Audit, *store.AuditRepo, func()) {
	t.Helper()
	path := filepath.Join(t.TempDir(), "test.db")
	db, err := store.Open(path)
	if err != nil {
		t.Fatalf("open db: %v", err)
	}
	repo := store.NewAuditRepo(db)
	eng := engine.NewAudit(repo, nil)
	return New(eng), repo, func() { _ = db.Close() }
}

func TestAudit_Emit(t *testing.T) {
	a, repo, cleanup := newTestAudit(t)
	defer cleanup()
	ctx := context.Background()
	a.Emit(ctx, ActionCertIssued, "cert:node-1", ResultSuccess, map[string]any{"cn": "node-1"})

	entries, err := repo.List(ctx, 10)
	if err != nil {
		t.Fatalf("List: %v", err)
	}
	if len(entries) != 1 {
		t.Fatalf("expected 1 audit entry, got %d", len(entries))
	}
	e := entries[0]
	if e.Action != string(ActionCertIssued) {
		t.Errorf("action: got %q, want %q", e.Action, ActionCertIssued)
	}
	if e.Result != string(ResultSuccess) {
		t.Errorf("result: got %q, want %q", e.Result, ResultSuccess)
	}
	if e.Resource != "cert:node-1" {
		t.Errorf("resource: got %q, want cert:node-1", e.Resource)
	}
	if e.Actor != "security" {
		t.Errorf("actor: got %q, want security", e.Actor)
	}
	if e.Error != "" {
		t.Errorf("error: got %q, want empty", e.Error)
	}
}

func TestAudit_EmitWithErr(t *testing.T) {
	a, repo, cleanup := newTestAudit(t)
	defer cleanup()
	ctx := context.Background()
	a.EmitWithErr(ctx, ActionNodeHandshakeFail, "hs:node-2", errors.New("bad cert"), nil)

	entries, err := repo.List(ctx, 10)
	if err != nil {
		t.Fatalf("List: %v", err)
	}
	if len(entries) != 1 {
		t.Fatalf("expected 1 audit entry, got %d", len(entries))
	}
	e := entries[0]
	if e.Result != string(ResultFailure) {
		t.Errorf("result: got %q, want %q", e.Result, ResultFailure)
	}
	if !strings.Contains(e.Error, "bad cert") {
		t.Errorf("error: got %q, want it to contain 'bad cert'", e.Error)
	}
}

func TestAudit_LogHandshakeOK(t *testing.T) {
	var buf bytes.Buffer
	logger := slog.New(slog.NewTextHandler(&buf, nil))
	LogHandshakeOK(logger, "peer-1", "AA:BB:CC")
	out := buf.String()
	for _, want := range []string{"event=mtls.handshake", "result=ok", "peer=peer-1", "cert_fp=AA:BB:CC"} {
		if !strings.Contains(out, want) {
			t.Errorf("LogHandshakeOK: output missing %q\noutput: %s", want, out)
		}
	}
}

func TestAudit_LogHandshakeFailed(t *testing.T) {
	var buf bytes.Buffer
	logger := slog.New(slog.NewTextHandler(&buf, nil))
	LogHandshakeFailed(logger, "peer-2", "", errors.New("tls: handshake"))
	out := buf.String()
	for _, want := range []string{"event=mtls.handshake", "result=failed", "peer=peer-2", "err=\"tls: handshake\""} {
		if !strings.Contains(out, want) {
			t.Errorf("LogHandshakeFailed: output missing %q\noutput: %s", want, out)
		}
	}
}

func TestAudit_LogHandshake_NilLogger(t *testing.T) {
	LogHandshakeOK(nil, "p", "fp")
	LogHandshakeFailed(nil, "p", "fp", errors.New("x"))
}

func TestAudit_NilSafe(t *testing.T) {
	var a *Audit
	a.Emit(context.Background(), ActionCertIssued, "x", ResultSuccess, nil)
	a.EmitWithErr(context.Background(), ActionCertIssued, "x", errors.New("y"), nil)
}

func TestAction_String(t *testing.T) {
	if got := ActionCertIssued.String(); got != "cert.issued" {
		t.Errorf("ActionCertIssued.String(): got %q, want cert.issued", got)
	}
	if got := ActionNodeHandshakeOK.String(); got != "node.handshake_ok" {
		t.Errorf("ActionNodeHandshakeOK.String(): got %q, want node.handshake_ok", got)
	}
}

func TestResult_String(t *testing.T) {
	if got := ResultSuccess.String(); got != "success" {
		t.Errorf("ResultSuccess.String(): got %q, want success", got)
	}
	if got := ResultFailure.String(); got != "failure" {
		t.Errorf("ResultFailure.String(): got %q, want failure", got)
	}
}

func TestFormatAction(t *testing.T) {
	got := FormatAction(ActionCertIssued, ResultSuccess)
	want := "action=cert.issued result=success"
	if got != want {
		t.Errorf("FormatAction: got %q, want %q", got, want)
	}
}
