package signedjobs

import (
	"context"
	"testing"
	"time"
)

// TASK D1 Group B — the agent_update op class RIDES the same LOCKED gate pipeline as every other
// destructive op. These tests use the REAL authz.Verifier + reconcile.Gate (via newRealGateRunner)
// over genuinely-minted signed blobs, asserting agent_update is gated identically to storage_wipe.

const agentUpdateParamsJSON = `{"version":"0.70.1","sha256":"` +
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + `"}`

// A correctly-signed agent_update by the PINNED operational key reaches the executor (a fake here;
// the real executor is unit-tested in internal/selfupdate). Proves the class is authorized, not
// silently dropped.
func TestRunner_ValidSignedAgentUpdateExecutes(t *testing.T) {
	s := newTestSigner(t)
	r, src, exec := newRealGateRunner(t, s)
	now := time.Now().UTC()
	src.add(mintJobOp(t, s, "agent_update", "au1", testHost, "", "ops-1", agentUpdateParamsJSON, now, now.Add(time.Hour)))

	if _, err := r.RunOnce(context.Background()); err != nil {
		t.Fatalf("RunOnce: %v", err)
	}
	if exec.count() != 1 || exec.calls[0] != "agent_update" {
		t.Fatalf("executor calls = %v, want one agent_update", exec.calls)
	}
	if !src.wasCompleted("au1") {
		t.Error("valid agent_update job not cleared")
	}
}

// A NON-PINNED signer's agent_update is REJECTED — the executor is never called. This is the
// companion to the class-allowlist question: agent_update is classified Destructive
// (reconcile.Classify), so an unsigned/wrong-key op cannot reach the binary swap. If the class were
// ever mis-classified Benign, this signature check would be bypassed and the test would fail.
func TestRunner_NonPinnedAgentUpdateRejected(t *testing.T) {
	pinned := newTestSigner(t)
	attacker := newTestSigner(t) // not pinned
	r, src, exec := newRealGateRunner(t, pinned)
	now := time.Now().UTC()
	src.add(mintJobOp(t, attacker, "agent_update", "au1", testHost, "", "ops-1", agentUpdateParamsJSON, now, now.Add(time.Hour)))

	r.RunOnce(context.Background())
	if exec.count() != 0 {
		t.Errorf("a non-pinned agent_update was EXECUTED (count=%d) — the binary swap must be gated", exec.count())
	}
	if !src.wasCompleted("au1") {
		t.Error("rejected agent_update job should be cleared")
	}
}

// An agent_update targeting ANOTHER host is rejected on this host (anti-retarget) — an operator
// can't accidentally push a build to the wrong box.
func TestRunner_AgentUpdateRetargetRejected(t *testing.T) {
	s := newTestSigner(t)
	r, src, exec := newRealGateRunner(t, s)
	now := time.Now().UTC()
	src.add(mintJobOp(t, s, "agent_update", "au1", "some-other-host", "", "ops-1", agentUpdateParamsJSON, now, now.Add(time.Hour)))

	r.RunOnce(context.Background())
	if exec.count() != 0 {
		t.Errorf("an agent_update for another host was executed here (count=%d)", exec.count())
	}
}
