import assert from "node:assert/strict";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, it } from "node:test";

import type { DispatchAttempt, ExecutionLease } from "../../scheduler/DispatchAttemptStore.ts";
import type { SessionRecord } from "../../types/state.ts";
import {
  buildCanonicalTaskRuntimeSnapshot,
  resolveCanonicalTaskRuntimeState,
  type CanonicalFormalReport,
} from "../resolveCanonicalTaskRuntimeState.ts";
import type { WorkerReceiptDurableHints } from "../workerReceiptDurableHints.ts";

const NOW = new Date("2026-08-10T08:00:00.000Z");
const TASK = "TASK-20260810-001";

function session(input: {
  sessionId: string;
  agentId: string;
  taskId?: string;
  role?: string;
  provider?: string;
  attemptId?: string;
  leaseId?: string;
  status?: string;
  kind?: "TASK_BOUND" | "CHAT_BOUND";
}): SessionRecord {
  return {
    protocol: {
      session_id: input.sessionId,
      agent_id: input.agentId,
      task_id: input.taskId ?? TASK,
      status: input.status ?? "running",
      started_at: "2026-08-10T07:59:55.000Z",
      runs: [{ run_id: `run-${input.sessionId}`, status: "running" }],
    },
    runtime_session_kind: input.kind ?? "TASK_BOUND",
    runtime_role: input.role ?? "DEV",
    runtime_provider: input.provider ?? "cursor",
    runtime_attempt_id: input.attemptId,
    runtime_lease_id: input.leaseId,
    runtime_last_event_at: "2026-08-10T07:59:59.000Z",
  } as unknown as SessionRecord;
}

function lease(input: Partial<ExecutionLease> = {}): ExecutionLease {
  return {
    lease_id: "lease-1",
    task_id: TASK,
    attempt_id: "attempt-1",
    agent_id: "DEV-02",
    session_id: "session-1",
    acquired_at: "2026-08-10T07:59:55.000Z",
    expires_at: "2026-08-10T08:29:55.000Z",
    ...input,
  };
}

function attempt(input: Partial<DispatchAttempt> = {}): DispatchAttempt {
  return {
    attempt_id: "attempt-1",
    task_id: TASK,
    task_path: `fcop/_lifecycle/active/${TASK}.md`,
    target_role: "DEV",
    target_agent_id: "DEV-02",
    source: "test",
    mode: "initial",
    status: "running",
    created_at: "2026-08-10T07:59:50.000Z",
    updated_at: "2026-08-10T07:59:56.000Z",
    session_id: "session-1",
    lease_id: "lease-1",
    ...input,
  };
}

function durable(input: Partial<WorkerReceiptDurableHints> = {}): WorkerReceiptDurableHints {
  return {
    nudgeCount: 0,
    sessionFailed: false,
    workerFailedPersisted: false,
    lastSessionStatus: null,
    lastSessionId: null,
    lastFailureCode: null,
    lastFailureCategory: null,
    isFirstTurnAbort: false,
    sessionUnsettled: false,
    historicalFailureRecoverable: false,
    recoverable: false,
    summary: {
      lastSessionId: null,
      lastStartedAt: null,
      lastEndedAt: null,
      lastSessionStatus: null,
      lastFailureCode: null,
      lastFailureCategory: null,
      isFirstTurnAbort: false,
      reportWrittenOnEnd: false,
      sessionUnsettled: false,
      agentRunning: false,
    },
    ...input,
  };
}

describe("resolveCanonicalTaskRuntimeState", () => {
  it("live TASK_BOUND session plus lease wins over durable unsettled history", () => {
    const live = session({
      sessionId: "session-1",
      agentId: "DEV-02",
      attemptId: "attempt-1",
      leaseId: "lease-1",
    });
    const snapshot = resolveCanonicalTaskRuntimeState({
      taskId: TASK,
      role: "DEV",
      liveSessions: [live],
      allSessions: [live],
      attempts: [attempt()],
      activeLease: lease(),
      durable: durable({
        sessionUnsettled: true,
        lastSessionId: "session-old",
        summary: {
          ...durable().summary,
          lastSessionId: "session-old",
          lastStartedAt: "2026-08-10T07:00:00.000Z",
          sessionUnsettled: true,
        },
      }),
      now: NOW,
    });
    assert.equal(snapshot.state, "running");
    assert.equal(snapshot.agent_id, "DEV-02");
    assert.equal(snapshot.session_id, "session-1");
    assert.equal(snapshot.conflicts.length, 0);
  });

  it("does not let an old same-role registry row hide the task Session agent", () => {
    const live = session({ sessionId: "session-2", agentId: "DEV-02" });
    const snapshot = resolveCanonicalTaskRuntimeState({
      taskId: TASK,
      role: "DEV",
      agentId: "DEV-01",
      liveSessions: [live],
      allSessions: [live],
      registry: [
        { agent_id: "DEV-01", role: "DEV", status: "running" },
        { agent_id: "DEV-02", role: "DEV", status: "running" },
      ],
      now: NOW,
    });
    assert.equal(snapshot.state, "running");
    assert.equal(snapshot.agent_id, "DEV-02");
  });

  it("classifies another FIFO task on the same agent as waiting_capacity", () => {
    const other = session({
      sessionId: "session-other",
      agentId: "DEV-02",
      taskId: "TASK-20260810-000",
    });
    const snapshot = resolveCanonicalTaskRuntimeState({
      taskId: TASK,
      role: "DEV",
      agentId: "DEV-02",
      liveSessions: [other],
      allSessions: [other],
      lifecycleBucket: "inbox",
      now: NOW,
    });
    assert.equal(snapshot.state, "waiting_capacity");
    assert.equal(snapshot.reason_code, "agent_running_other_fifo_task");
  });

  it("classifies a fresh valid lease without Session as settling", () => {
    const snapshot = resolveCanonicalTaskRuntimeState({
      taskId: TASK,
      role: "DEV",
      activeLease: lease(),
      attempts: [attempt({ status: "claimed" })],
      liveSessions: [],
      now: NOW,
      startupGraceMs: 30_000,
    });
    assert.equal(snapshot.state, "settling");
    assert.equal(snapshot.suggested_action, "wait");
  });

  it("classifies a running attempt whose lease expired as recoverable", () => {
    const snapshot = resolveCanonicalTaskRuntimeState({
      taskId: TASK,
      role: "DEV",
      attempts: [attempt({ status: "running" })],
      liveSessions: [],
      activeLease: null,
      lifecycleBucket: "active",
      now: NOW,
    });
    assert.equal(snapshot.state, "recoverable");
    assert.equal(snapshot.reason_code, "attempt_without_active_lease");
  });

  it("stops on a valid lease without Session after startup grace", () => {
    const snapshot = resolveCanonicalTaskRuntimeState({
      taskId: TASK,
      role: "DEV",
      activeLease: lease({ acquired_at: "2026-08-10T07:50:00.000Z" }),
      attempts: [attempt({ status: "claimed" })],
      liveSessions: [],
      now: NOW,
      startupGraceMs: 30_000,
    });
    assert.equal(snapshot.state, "projection_conflict");
    assert.equal(snapshot.suggested_action, "inspect_conflict");
  });

  it("a formal REPORT prevents technical recovery", () => {
    const report: CanonicalFormalReport = {
      report_id: "REPORT-20260810-001-DEV-to-PM",
      task_id: TASK,
      status: "failed",
      sender: "DEV",
      valid: true,
    };
    const snapshot = resolveCanonicalTaskRuntimeState({
      taskId: TASK,
      role: "DEV",
      formalReport: report,
      attempts: [attempt({ status: "session_failed" })],
      durable: durable({
        sessionFailed: true,
        historicalFailureRecoverable: true,
        recoverable: true,
      }),
      now: NOW,
    });
    assert.equal(snapshot.state, "failed");
    assert.equal(snapshot.suggested_action, "review_report");
    assert.equal(snapshot.business_decision, null);
  });

  it("lets the current-round REPORT win while its matching Session is settling", () => {
    const live = session({
      sessionId: "session-1",
      agentId: "DEV-02",
      attemptId: "attempt-1",
      leaseId: "lease-1",
    });
    const snapshot = resolveCanonicalTaskRuntimeState({
      taskId: TASK,
      role: "DEV",
      formalReport: {
        report_id: "REPORT-20260810-001-DEV-to-PM",
        task_id: TASK,
        status: "done",
        session_id: "session-1",
        run_id: "run-session-1",
        attempt_id: "attempt-1",
        lease_id: "lease-1",
        agent_id: "DEV-02",
        valid: true,
      },
      liveSessions: [live],
      activeLease: lease(),
      attempts: [attempt()],
      now: NOW,
    });
    assert.equal(snapshot.state, "done");
    assert.equal(snapshot.has_formal_report, true);
    assert.equal(snapshot.conflicts.length, 0);
  });

  it("rejects a stale REPORT whose attempt binding is not the current round", async () => {
    const root = await mkdtemp(join(tmpdir(), "cfm-canonical-report-"));
    try {
      const reportsDir = join(root, "fcop", "reports");
      await mkdir(reportsDir, { recursive: true });
      await writeFile(
        join(reportsDir, "REPORT-20260810-001-DEV-to-PM.md"),
        [
          "---",
          "report_id: REPORT-20260810-001-DEV-to-PM",
          `task_id: ${TASK}`,
          "sender: DEV",
          "recipient: PM",
          "status: done",
          "session_id: session-old",
          "attempt_id: attempt-old",
          "lease_id: lease-old",
          "---",
          "# stale report",
          "previous execution round only",
        ].join("\n"),
        "utf-8",
      );
      const snapshot = await buildCanonicalTaskRuntimeSnapshot({
        projectRoot: root,
        taskId: TASK,
        role: "DEV",
        durable: durable(),
        sessionManager: {
          listActive: async () => [],
          listLive: async () => [],
        },
        sessionStore: { listAll: async () => [] },
        dispatcher: {
          getDispatchTaskState: async () => ({
            task_id: TASK,
            state: "running",
            lifecycle_bucket: "active",
            attempts: [
              attempt({
                attempt_id: "attempt-new",
                session_id: "session-new",
                lease_id: "lease-new",
                status: "running",
              }),
            ],
          }),
        },
        now: NOW,
      });
      assert.equal(snapshot.has_formal_report, false);
      assert.equal(snapshot.state, "recoverable");
      assert.equal(snapshot.attempt_id, "attempt-new");
    } finally {
      await rm(root, { recursive: true, force: true });
    }
  });

  it("loads the current bound REPORT from an independent project root", async () => {
    const root = await mkdtemp(join(tmpdir(), "cfm-canonical-current-report-"));
    try {
      const reportsDir = join(root, "fcop", "reports");
      await mkdir(reportsDir, { recursive: true });
      await writeFile(
        join(reportsDir, "REPORT-20260810-002-DEV-to-PM.md"),
        [
          "---",
          "report_id: REPORT-20260810-002-DEV-to-PM",
          `task_id: ${TASK}`,
          "sender: DEV",
          "recipient: PM",
          "status: done",
          "session_id: session-current",
          "attempt_id: attempt-current",
          "lease_id: lease-current",
          "agent_id: DEV-02",
          "---",
          "# current report",
          "current execution round completed",
        ].join("\n"),
        "utf-8",
      );
      const snapshot = await buildCanonicalTaskRuntimeSnapshot({
        projectRoot: root,
        taskId: TASK,
        role: "DEV",
        durable: durable(),
        sessionManager: {
          listActive: async () => [],
          listLive: async () => [],
        },
        sessionStore: { listAll: async () => [] },
        dispatcher: {
          getDispatchTaskState: async () => ({
            task_id: TASK,
            state: "waiting_report",
            lifecycle_bucket: "review",
            attempts: [
              attempt({
                attempt_id: "attempt-current",
                session_id: "session-current",
                lease_id: "lease-current",
                status: "reported",
              }),
            ],
          }),
        },
        now: NOW,
      });
      assert.equal(snapshot.state, "done");
      assert.equal(snapshot.report_id, "REPORT-20260810-002-DEV-to-PM");
      assert.equal(snapshot.attempt_id, "attempt-current");
      assert.equal(snapshot.has_formal_report, true);
    } finally {
      await rm(root, { recursive: true, force: true });
    }
  });

  it("Cursor and Codex Hosts resolve the same runtime facts identically", () => {
    const cursor = session({ sessionId: "session-1", agentId: "DEV-02", provider: "cursor" });
    const codex = session({ sessionId: "session-1", agentId: "DEV-02", provider: "openai-chatgpt-subscription" });
    const base = { taskId: TASK, role: "DEV", now: NOW };
    const cursorState = resolveCanonicalTaskRuntimeState({ ...base, liveSessions: [cursor] });
    const codexState = resolveCanonicalTaskRuntimeState({ ...base, liveSessions: [codex] });
    assert.equal(cursorState.state, "running");
    assert.equal(codexState.state, cursorState.state);
    assert.equal(codexState.reason_code, cursorState.reason_code);
  });
});
