package authz

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

func TestMemoryNonceStore(t *testing.T) {
	m := NewMemoryNonceStore()
	exp := time.Now().Add(time.Hour)
	if m.SeenOrRecord("a", exp) {
		t.Fatal("first record should be unseen")
	}
	if !m.SeenOrRecord("a", exp) {
		t.Fatal("second record should be seen")
	}
	if m.SeenOrRecord("b", exp) {
		t.Fatal("distinct nonce should be unseen")
	}
}

func TestFileNonceStore_RecordAndReload(t *testing.T) {
	path := filepath.Join(t.TempDir(), "nonces.log")
	exp := refNow.Add(time.Hour)

	s1, err := OpenFileNonceStore(path)
	if err != nil {
		t.Fatal(err)
	}
	if s1.SeenOrRecord("dead", exp) {
		t.Fatal("first record should be unseen")
	}
	if err := s1.Close(); err != nil {
		t.Fatal(err)
	}

	// Reopen: the recorded nonce must still be seen (durable across restart).
	s2, err := OpenFileNonceStore(path)
	if err != nil {
		t.Fatal(err)
	}
	defer s2.Close()
	if !s2.SeenOrRecord("dead", exp) {
		t.Fatal("nonce not durable across reopen")
	}
}

func TestFileNonceStore_CompactionPrunesExpiredOnly(t *testing.T) {
	path := filepath.Join(t.TempDir(), "nonces.log")
	s, err := OpenFileNonceStore(path)
	if err != nil {
		t.Fatal(err)
	}
	s.now = func() time.Time { return refNow }
	s.CompactEvery = 2 // force a compaction after two appends

	s.SeenOrRecord("expired", refNow.Add(-time.Hour)) // exp in the past
	s.SeenOrRecord("live", refNow.Add(time.Hour))     // triggers compaction
	if err := s.Close(); err != nil {
		t.Fatal(err)
	}

	// Reopen: the live nonce survived, the expired one was pruned (housekeeping;
	// an expired op is rejected by the time check before the nonce check anyway).
	s2, err := OpenFileNonceStore(path)
	if err != nil {
		t.Fatal(err)
	}
	defer s2.Close()
	if !s2.SeenOrRecord("live", refNow.Add(time.Hour)) {
		t.Error("live nonce should have survived compaction")
	}
	if s2.SeenOrRecord("expired", refNow.Add(-time.Hour)) {
		t.Error("expired nonce should have been pruned (was still present)")
	}
}

func TestFileNonceStore_SkipsTornLine(t *testing.T) {
	path := filepath.Join(t.TempDir(), "nonces.log")
	// a valid record line + a torn/garbage trailing line from a hypothetical crash
	content := `{"n":"good","e":"` + refNow.Add(time.Hour).Format(time.RFC3339Nano) + `"}` + "\n" + `{"n":"tor`
	if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
		t.Fatal(err)
	}
	s, err := OpenFileNonceStore(path)
	if err != nil {
		t.Fatalf("open with torn line should not fail: %v", err)
	}
	defer s.Close()
	if !s.SeenOrRecord("good", refNow.Add(time.Hour)) {
		t.Error("valid record before the torn line should have loaded")
	}
}
