package reconcile

import (
	"context"
	"errors"
	"path/filepath"
	"testing"

	"gitea.dooplex.hu/admin/felhom-agent/internal/authz"
)

// newSignedEngine builds an engine whose gate has a real verifier pinning one
// operational key — for exercising the signed-job consuming layer end to end.
func newSignedEngine(t *testing.T, api GuestAPI) (*Engine, *Journal, testSigner) {
	t.Helper()
	j, err := OpenJournal(filepath.Join(t.TempDir(), "journal.log"))
	if err != nil {
		t.Fatalf("OpenJournal: %v", err)
	}
	t.Cleanup(func() { j.Close() })
	q := NewQueue()
	t.Cleanup(q.Close)
	op := newTestSigner(t)
	v, _ := realVerifierAt(t, filepath.Join(t.TempDir(), "n.log"), testHost, op.allowed(t, "op1", authz.RoleOperational))
	g := NewGate(v, testHost, nil, nil)
	e := NewEngine(EngineOptions{API: api, Queue: q, Journal: j, Gate: g, HostID: testHost})
	return e, j, op
}

func TestRunSignedJob_ValidExecutesAndMarksApplied(t *testing.T) {
	e, j, op := newSignedEngine(t, &fakeAPI{})
	issued, expires := freshWindow()
	n := nonce()
	signed := op.mint("guest_destroy", testHost, "9001", "op1", n, `{"purge":true}`, issued, expires)

	calls := 0
	exec := func(context.Context, Intent, *authz.VerifiedOp) (string, error) { calls++; return "", nil } // synchronous

	res := e.RunSignedJob(context.Background(), destroyIntent(SourceOneShotJob), signed, exec)
	if !res.Executed || res.Err != nil {
		t.Fatalf("valid job should execute, got %+v", res)
	}
	if calls != 1 {
		t.Errorf("executor should run once, ran %d", calls)
	}
	if !j.AlreadyApplied(n) {
		t.Error("successful job must mark its idempotency key (nonce) applied")
	}
}

func TestRunSignedJob_RedeliveryDedupedByIdempotencyKey(t *testing.T) {
	// After success, a redelivered identical job must NOT re-run — the journal's
	// idempotency key short-circuits BEFORE the verifier (so it reports already-applied,
	// not a confusing replay rejection).
	e, _, op := newSignedEngine(t, &fakeAPI{})
	issued, expires := freshWindow()
	n := nonce()
	signed := op.mint("guest_destroy", testHost, "9001", "op1", n, `{"purge":true}`, issued, expires)

	calls := 0
	exec := func(context.Context, Intent, *authz.VerifiedOp) (string, error) { calls++; return "", nil }

	first := e.RunSignedJob(context.Background(), destroyIntent(SourceOneShotJob), signed, exec)
	if !first.Executed {
		t.Fatalf("first delivery should execute: %+v", first)
	}
	second := e.RunSignedJob(context.Background(), destroyIntent(SourceOneShotJob), signed, exec)
	if !second.AlreadyApplied || second.Executed {
		t.Fatalf("redelivery should be deduped (already applied), got %+v", second)
	}
	if calls != 1 {
		t.Errorf("executor must run exactly once across redelivery, ran %d", calls)
	}
}

func TestRunSignedJob_RefusedDoesNotExecute(t *testing.T) {
	e, j, _ := newSignedEngine(t, &fakeAPI{})
	attacker := newTestSigner(t) // not pinned
	issued, expires := freshWindow()
	n := nonce()
	signed := attacker.mint("guest_destroy", testHost, "9001", "op1", n, `{"purge":true}`, issued, expires)

	calls := 0
	exec := func(context.Context, Intent, *authz.VerifiedOp) (string, error) { calls++; return "", nil }

	res := e.RunSignedJob(context.Background(), destroyIntent(SourceOneShotJob), signed, exec)
	if res.Executed || res.Decision.Allowed || !errors.Is(res.Decision.Err, authz.ErrUnknownSigner) {
		t.Fatalf("forged job must be refused unexecuted, got %+v", res)
	}
	if calls != 0 {
		t.Errorf("executor must not run for a refused job, ran %d", calls)
	}
	if j.AlreadyApplied(n) {
		t.Error("a refused job must not mark its key applied")
	}
}

func TestRunSignedJob_NoExecutorInert(t *testing.T) {
	// Slice-4 inert state: a VALID authorization with no destructive executor wired
	// returns an error and does NOT mark the key applied (so it is retryable once the
	// executor lands in a later slice).
	e, j, op := newSignedEngine(t, &fakeAPI{})
	issued, expires := freshWindow()
	n := nonce()
	signed := op.mint("guest_destroy", testHost, "9001", "op1", n, `{"purge":true}`, issued, expires)

	res := e.RunSignedJob(context.Background(), destroyIntent(SourceOneShotJob), signed, nil)
	if !res.Decision.Allowed {
		t.Fatalf("op should authorize even with no executor: %+v", res.Decision)
	}
	if res.Executed || res.Err == nil {
		t.Fatalf("no-executor job should not execute and should error, got %+v", res)
	}
	if j.AlreadyApplied(n) {
		t.Error("an unexecuted (no-executor) job must not mark its key applied")
	}
}

func TestRunSignedJob_ExecutorErrorJournaledFailed(t *testing.T) {
	e, j, op := newSignedEngine(t, &fakeAPI{})
	issued, expires := freshWindow()
	n := nonce()
	signed := op.mint("guest_destroy", testHost, "9001", "op1", n, `{"purge":true}`, issued, expires)

	exec := func(context.Context, Intent, *authz.VerifiedOp) (string, error) {
		return "", errors.New("destroy failed")
	}
	res := e.RunSignedJob(context.Background(), destroyIntent(SourceOneShotJob), signed, exec)
	if res.Executed || res.Err == nil {
		t.Fatalf("executor error should propagate, got %+v", res)
	}
	if j.AlreadyApplied(n) {
		t.Error("a failed execution must not mark its key applied")
	}
}
