package offsite

import (
	"io"
	"log"
	"path/filepath"
	"testing"
	"time"

	"gitea.dooplex.hu/admin/felhom-hub/internal/store"
)

// R-70 detector tests — four states + the applied-wins precedence + the demo-felhom
// applied+stale-staged edge. Fixtures are the real store (t.TempDir SQLite), timestamps
// controlled via the test-only back-dater (no sleeps).

func newDeliveryStore(t *testing.T) *store.Store {
	t.Helper()
	st, err := store.New(filepath.Join(t.TempDir(), "d.db"), log.New(io.Discard, "", 0))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { st.Close() })
	return st
}

const (
	reportWithOffsite = `{"health":{"status":"ok"},"offsite":{"enabled":true,"escrow_state":"escrowed"}}`
	reportNoOffsite   = `{"health":{"status":"ok"}}`
)

func TestDeliveryState_Applied(t *testing.T) {
	st := newDeliveryStore(t)
	if err := st.SaveReport("c1", []byte(reportWithOffsite)); err != nil {
		t.Fatal(err)
	}
	if err := st.SaveOneTimeSecret("c1", "x"); err != nil {
		t.Fatal(err)
	}
	if _, err := st.ConsumeOneTimeSecret("c1"); err != nil {
		t.Fatal(err)
	}
	got, err := DeliveryStateFor(st, "c1")
	if err != nil {
		t.Fatal(err)
	}
	if got.State != DeliveryApplied {
		t.Fatalf("state = %s, want applied", got.State)
	}
	if !got.StaleStagedSince.IsZero() {
		t.Fatalf("consumed secret must not flag stale-staged, got %v", got.StaleStagedSince)
	}
	if got.Since.IsZero() {
		t.Fatal("applied state must carry the report timestamp (mióta)")
	}
}

// The demo-felhom shape: applied via an earlier generation + an unconsumed secret staged since.
// Precedence: applied WINS, and the stale staged secret becomes a VISIBLE info flag — never a
// state downgrade, never a heal trigger.
func TestDeliveryState_AppliedWithStaleStaged(t *testing.T) {
	st := newDeliveryStore(t)
	if err := st.SaveReport("c1", []byte(reportWithOffsite)); err != nil {
		t.Fatal(err)
	}
	if err := st.SaveOneTimeSecret("c1", "stale"); err != nil {
		t.Fatal(err)
	}
	if err := st.SetOneTimeSecretTimesForTest("c1", "2026-07-21 08:29:29", ""); err != nil {
		t.Fatal(err)
	}
	got, err := DeliveryStateFor(st, "c1")
	if err != nil {
		t.Fatal(err)
	}
	if got.State != DeliveryApplied {
		t.Fatalf("precedence broken: state = %s, want applied (applied wins over staged)", got.State)
	}
	want := time.Date(2026, 7, 21, 8, 29, 29, 0, time.UTC)
	if !got.StaleStagedSince.Equal(want) {
		t.Fatalf("StaleStagedSince = %v, want %v (the invisible plumbing must be visible)", got.StaleStagedSince, want)
	}
}

// The burned-credential shape (F10): consumed, and every report since carries no offbox.
func TestDeliveryState_ConsumedAwaitingApply(t *testing.T) {
	st := newDeliveryStore(t)
	if err := st.SaveOneTimeSecret("c1", "x"); err != nil {
		t.Fatal(err)
	}
	// consumed 2h ago; the reports below (received "now") all come AFTER it
	if err := st.SetOneTimeSecretTimesForTest("c1", "2026-01-01 00:00:00", "2026-01-01 00:03:00"); err != nil {
		t.Fatal(err)
	}
	for i := 0; i < 5; i++ {
		if err := st.SaveReport("c1", []byte(reportNoOffsite)); err != nil {
			t.Fatal(err)
		}
	}
	got, err := DeliveryStateFor(st, "c1")
	if err != nil {
		t.Fatal(err)
	}
	if got.State != DeliveryConsumedAwaitingApply {
		t.Fatalf("state = %s, want consumed_awaiting_apply", got.State)
	}
	if got.ReportsSinceConsume != 5 || got.OffsiteReportsSinceConsume != 0 {
		t.Fatalf("counts = %d/%d, want 5/0", got.ReportsSinceConsume, got.OffsiteReportsSinceConsume)
	}
	if want := time.Date(2026, 1, 1, 0, 3, 0, 0, time.UTC); !got.Since.Equal(want) {
		t.Fatalf("Since = %v, want consumed_at %v", got.Since, want)
	}
}

// The since-filter: reports received BEFORE consumed_at must not count toward the trigger.
func TestDeliveryState_ReportsBeforeConsumeNotCounted(t *testing.T) {
	st := newDeliveryStore(t)
	for i := 0; i < 3; i++ {
		if err := st.SaveReport("c1", []byte(reportNoOffsite)); err != nil {
			t.Fatal(err)
		}
	}
	if err := st.SaveOneTimeSecret("c1", "x"); err != nil {
		t.Fatal(err)
	}
	// consumed FAR in the future relative to the rows above → zero reports since
	if err := st.SetOneTimeSecretTimesForTest("c1", "2099-01-01 00:00:00", "2099-01-01 00:00:01"); err != nil {
		t.Fatal(err)
	}
	got, err := DeliveryStateFor(st, "c1")
	if err != nil {
		t.Fatal(err)
	}
	if got.State != DeliveryConsumedAwaitingApply || got.ReportsSinceConsume != 0 {
		t.Fatalf("state/count = %s/%d, want consumed_awaiting_apply/0 (pre-consume reports must not count)",
			got.State, got.ReportsSinceConsume)
	}
}

func TestDeliveryState_StagedAwaitingConsume(t *testing.T) {
	st := newDeliveryStore(t)
	if err := st.SaveReport("c1", []byte(reportNoOffsite)); err != nil {
		t.Fatal(err)
	}
	if err := st.SaveOneTimeSecret("c1", "x"); err != nil {
		t.Fatal(err)
	}
	got, err := DeliveryStateFor(st, "c1")
	if err != nil {
		t.Fatal(err)
	}
	if got.State != DeliveryStagedAwaitingConsume {
		t.Fatalf("state = %s, want staged_awaiting_consume", got.State)
	}
	if got.Since.IsZero() {
		t.Fatal("staged state must carry created_at (mióta)")
	}
}

func TestDeliveryState_NoSecret(t *testing.T) {
	st := newDeliveryStore(t)
	if err := st.SaveReport("c1", []byte(reportNoOffsite)); err != nil {
		t.Fatal(err)
	}
	got, err := DeliveryStateFor(st, "c1")
	if err != nil {
		t.Fatal(err)
	}
	if got.State != DeliveryNoSecret {
		t.Fatalf("state = %s, want no_secret (mis-state must render needs-attention, never applied)", got.State)
	}
}
