package store

import (
	"context"
	"encoding/json"
	"path/filepath"
	"testing"
	"time"

	"github.com/oklog/ulid/v2"
)

func newAuditTestStore(t *testing.T) (*Store, string) {
	t.Helper()
	st, err := Open(context.Background(), filepath.Join(t.TempDir(), "rm.db"))
	if err != nil {
		t.Fatalf("open: %v", err)
	}
	t.Cleanup(func() { _ = st.Close() })
	uid := ulid.Make().String()
	if err := st.CreateUser(context.Background(), User{
		ID: uid, Username: "alice", PasswordHash: "x",
		Role: RoleOperator, CreatedAt: time.Now().UTC(),
	}); err != nil {
		t.Fatalf("create user: %v", err)
	}
	return st, uid
}

func appendAudit(t *testing.T, st *Store, uid, actor, action, targetKind, targetID string, ts time.Time) {
	t.Helper()
	var u, tk, ti *string
	if uid != "" {
		u = &uid
	}
	if targetKind != "" {
		tk = &targetKind
	}
	if targetID != "" {
		ti = &targetID
	}
	if err := st.AppendAudit(context.Background(), AuditEntry{
		ID: ulid.Make().String(), UserID: u, Actor: actor, Action: action,
		TargetKind: tk, TargetID: ti, TS: ts, Payload: json.RawMessage(`{}`),
	}); err != nil {
		t.Fatalf("append: %v", err)
	}
}

func TestListAuditFiltersAndOrdering(t *testing.T) {
	t.Parallel()
	st, uid := newAuditTestStore(t)
	t0 := time.Now().UTC()

	appendAudit(t, st, uid, "user", "host.enrolled", "host", "h1", t0.Add(-3*time.Hour))
	appendAudit(t, st, uid, "user", "alert.acknowledge", "alert", "a1", t0.Add(-2*time.Hour))
	appendAudit(t, st, uid, "user", "alert.resolve", "alert", "a1", t0.Add(-time.Hour))
	appendAudit(t, st, "", "system", "host.auto_init", "host", "h1", t0.Add(-30*time.Minute))

	all, err := st.ListAudit(context.Background(), AuditFilter{})
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	if len(all) != 4 {
		t.Fatalf("len: got %d want 4", len(all))
	}
	// Ordered ts DESC — most recent first.
	if all[0].Action != "host.auto_init" || all[3].Action != "host.enrolled" {
		t.Errorf("ordering: got %s ... %s", all[0].Action, all[3].Action)
	}

	// Action prefix filter: alert.* → 2 rows.
	got, err := st.ListAudit(context.Background(), AuditFilter{ActionLike: "alert."})
	if err != nil {
		t.Fatalf("filter alert.: %v", err)
	}
	if len(got) != 2 {
		t.Errorf("alert.* filter: got %d want 2", len(got))
	}

	// User filter excludes system rows.
	got, _ = st.ListAudit(context.Background(), AuditFilter{UserID: uid})
	if len(got) != 3 {
		t.Errorf("user filter: got %d want 3", len(got))
	}

	// Actor=system isolates the auto_init.
	got, _ = st.ListAudit(context.Background(), AuditFilter{Actor: "system"})
	if len(got) != 1 || got[0].Action != "host.auto_init" {
		t.Errorf("actor=system: got %+v", got)
	}

	// Target kind filter.
	got, _ = st.ListAudit(context.Background(), AuditFilter{TargetKind: "alert"})
	if len(got) != 2 {
		t.Errorf("target_kind=alert: got %d want 2", len(got))
	}

	// Time range: last 90m → resolve + auto_init.
	got, _ = st.ListAudit(context.Background(), AuditFilter{Since: t0.Add(-90 * time.Minute)})
	if len(got) != 2 {
		t.Errorf("since 90m: got %d want 2", len(got))
	}

	// Limit clamps result count.
	got, _ = st.ListAudit(context.Background(), AuditFilter{Limit: 2})
	if len(got) != 2 {
		t.Errorf("limit: got %d want 2", len(got))
	}
}

func TestListAuditSort(t *testing.T) {
	t.Parallel()
	st, uid := newAuditTestStore(t)
	t0 := time.Now().UTC()

	appendAudit(t, st, uid, "user", "host.enrolled", "host", "h1", t0.Add(-3*time.Hour))
	appendAudit(t, st, uid, "user", "alert.acknowledge", "alert", "a1", t0.Add(-time.Hour))
	appendAudit(t, st, "", "system", "host.auto_init", "host", "h1", t0.Add(-30*time.Minute))

	ctx := context.Background()

	// Sort by action ASC.
	got, err := st.ListAudit(ctx, AuditFilter{OrderBy: "action", OrderAsc: true})
	if err != nil {
		t.Fatalf("sort action asc: %v", err)
	}
	wantActions := []string{"alert.acknowledge", "host.auto_init", "host.enrolled"}
	for i, w := range wantActions {
		if got[i].Action != w {
			t.Errorf("[%d] action: got %q want %q", i, got[i].Action, w)
		}
	}

	// Sort by action DESC.
	got, _ = st.ListAudit(ctx, AuditFilter{OrderBy: "action", OrderAsc: false})
	if got[0].Action != "host.enrolled" {
		t.Errorf("desc head: got %q want host.enrolled", got[0].Action)
	}

	// Unknown OrderBy → falls back to ts DESC.
	got, _ = st.ListAudit(ctx, AuditFilter{OrderBy: "DROP TABLE; --"})
	if got[0].Action != "host.auto_init" {
		t.Errorf("unknown OrderBy should fall back to ts DESC; got head %q", got[0].Action)
	}

	// Sort by actor — ties tie-break on ts DESC, so 'user' rows
	// should come back newest-first within the actor group.
	got, _ = st.ListAudit(ctx, AuditFilter{OrderBy: "actor", OrderAsc: true})
	// First two are 'system' (1 row) and 'user' (2 rows newest-first):
	// expect system → user(ack) → user(enrolled)
	if got[0].Actor != "system" {
		t.Errorf("actor asc head: got %q want system", got[0].Actor)
	}
	if got[1].Action != "alert.acknowledge" {
		t.Errorf("actor asc tie-break should be ts DESC; got [1]=%q", got[1].Action)
	}
}

func TestDistinctAuditActions(t *testing.T) {
	t.Parallel()
	st, uid := newAuditTestStore(t)
	t0 := time.Now().UTC()

	appendAudit(t, st, uid, "user", "host.enrolled", "host", "h1", t0)
	appendAudit(t, st, uid, "user", "host.enrolled", "host", "h2", t0)
	appendAudit(t, st, uid, "user", "alert.acknowledge", "alert", "a1", t0)

	got, err := st.DistinctAuditActions(context.Background())
	if err != nil {
		t.Fatalf("distinct: %v", err)
	}
	want := []string{"alert.acknowledge", "host.enrolled"}
	if len(got) != len(want) {
		t.Fatalf("got %v want %v", got, want)
	}
	for i := range want {
		if got[i] != want[i] {
			t.Errorf("[%d]: got %q want %q", i, got[i], want[i])
		}
	}
}
