package auditlog

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

func newTestStore(t *testing.T) *Store {
	t.Helper()
	s, err := New(filepath.Join(t.TempDir(), "audit.db"))
	if err != nil {
		t.Fatal(err)
	}
	return s
}

// eventually polls List until at least want entries are persisted (writes are
// async) or it times out.
func eventually(t *testing.T, s *Store, want int) []Entry {
	t.Helper()
	deadline := time.Now().Add(time.Second)
	for {
		got, err := s.List(100)
		if err != nil {
			t.Fatal(err)
		}
		if len(got) >= want {
			return got
		}
		if time.Now().After(deadline) {
			t.Fatalf("timed out waiting for %d entries, have %d", want, len(got))
		}
		time.Sleep(5 * time.Millisecond)
	}
}

func TestRecordAndList(t *testing.T) {
	s := newTestStore(t)
	s.Record("alice", "POST", "/api/users", "users", 200)
	s.Record("bob", "DELETE", "/api/users/x", "users", 403)

	got := eventually(t, s, 2)
	// Newest first.
	if got[0].Username != "bob" || got[0].Status != 403 {
		t.Errorf("newest entry wrong: %+v", got[0])
	}
	if got[1].Username != "alice" || got[1].Method != "POST" {
		t.Errorf("oldest entry wrong: %+v", got[1])
	}
	if got[0].Time == "" {
		t.Error("time not populated")
	}
}

func TestListLimit(t *testing.T) {
	s := newTestStore(t)
	for range 5 {
		s.Record("u", "POST", "/api/x", "system", 200)
	}
	eventually(t, s, 5)
	got, err := s.List(3)
	if err != nil {
		t.Fatal(err)
	}
	if len(got) != 3 {
		t.Errorf("limit not honored: got %d, want 3", len(got))
	}
}

func TestCloseDrains(t *testing.T) {
	path := filepath.Join(t.TempDir(), "audit.db")
	s, err := New(path)
	if err != nil {
		t.Fatal(err)
	}
	for range 50 {
		s.Record("u", "POST", "/api/x", "system", 200)
	}
	// Close must flush everything still buffered before returning.
	if err := s.Close(); err != nil {
		t.Fatal(err)
	}

	// Reopen and confirm all 50 made it to disk.
	s2, err := New(path)
	if err != nil {
		t.Fatal(err)
	}
	got, err := s2.List(100)
	if err != nil {
		t.Fatal(err)
	}
	if len(got) != 50 {
		t.Errorf("Close did not drain: got %d entries, want 50", len(got))
	}
}

func TestListEmpty(t *testing.T) {
	got, err := newTestStore(t).List(10)
	if err != nil {
		t.Fatal(err)
	}
	if len(got) != 0 {
		t.Errorf("fresh store should be empty, got %d", len(got))
	}
}
