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 { resolveReviewEvidence } from "../ReviewEvidenceResolver.ts";

const TASK = "TASK-20260810-031";
const SESSION = "session-codex-31";
const ATTEMPT = "attempt-31";
const LEASE = "lease-31";

async function writeCoreEvidence(
  root: string,
  sessionStoreDir = join(root, ".codeflowmu", "state", "sessions"),
): Promise<void> {
  const sessions = sessionStoreDir;
  const dispatch = join(root, ".codeflowmu", "dispatch");
  const runtime = join(root, "fcop", "logs", "runtime");
  await Promise.all([
    mkdir(sessions, { recursive: true }),
    mkdir(dispatch, { recursive: true }),
    mkdir(runtime, { recursive: true }),
  ]);
  await writeFile(join(sessions, `${SESSION}.json`), JSON.stringify({
    protocol: {
      session_id: SESSION,
      agent_id: "DEV-01",
      task_id: TASK,
      status: "completed",
      runs: [{ run_id: "run-31", status: "completed" }],
    },
    runtime_session_kind: "TASK_BOUND",
    runtime_root_task_id: "TASK-20260810-030",
    runtime_thread_key: "thread-31",
    runtime_role: "DEV",
    runtime_attempt_id: ATTEMPT,
    runtime_lease_id: LEASE,
  }), "utf-8");
  await writeFile(join(dispatch, "dispatch-state.json"), JSON.stringify({
    version: 1,
    attempts: [{
      attempt_id: ATTEMPT,
      task_id: TASK,
      parent_task_id: "TASK-20260810-030",
      thread_key: "thread-31",
      target_role: "DEV",
      target_agent_id: "DEV-01",
      session_id: SESSION,
      lease_id: LEASE,
      status: "reported",
    }],
    leases: [{
      lease_id: LEASE,
      attempt_id: ATTEMPT,
      task_id: TASK,
      agent_id: "DEV-01",
      session_id: SESSION,
    }],
    decisions: [],
  }), "utf-8");
  await writeFile(join(runtime, "runtime-events-20260810.jsonl"), `${JSON.stringify({
    at: "2026-08-10T08:00:00.000Z",
    event_type: "runtime.session_ended",
    task_id: TASK,
    root_task_id: "TASK-20260810-030",
    thread_key: "thread-31",
    session_id: SESSION,
    run_id: "run-31",
    attempt_id: ATTEMPT,
    lease_id: LEASE,
    agent_id: "DEV-01",
    role: "DEV",
    payload: { status: "completed" },
  })}\n`, "utf-8");
}

describe("execution provenance in review evidence", () => {
  it("accepts SessionStore + AttemptStore + Runtime JSONL when Panel action log is absent", async () => {
    const root = await mkdtemp(join(tmpdir(), "codeflowmu-provenance-"));
    try {
      await writeCoreEvidence(root);
      const summary = resolveReviewEvidence({
        projectRoot: root,
        task_id: TASK,
        root_task_id: "TASK-20260810-030",
        thread_key: "thread-31",
        session_id: SESSION,
        run_id: "run-31",
        attempt_id: ATTEMPT,
        lease_id: LEASE,
        agent_id: "DEV-01",
        role: "DEV",
      });
      assert.equal(summary.session.found, true);
      assert.equal(summary.execution_provenance?.status, "observability_gap");
      assert.equal(summary.execution_provenance?.hard_sources_agree, 3);
      assert.ok(summary.warnings.includes("observability_gap: panel_action_log_absent"));
      assert.equal(summary.execution_provenance?.conflicts.length, 0);
    } finally {
      await rm(root, { recursive: true, force: true }).catch(() => undefined);
    }
  });

  it("resolves the Runtime SessionStore outside the project root", async () => {
    const root = await mkdtemp(join(tmpdir(), "codeflowmu-provenance-project-"));
    const runtimeData = await mkdtemp(join(tmpdir(), "codeflowmu-provenance-runtime-"));
    const sessions = join(runtimeData, "sessions");
    try {
      await writeCoreEvidence(root, sessions);
      await mkdir(join(root, ".codeflowmu"), { recursive: true });
      await writeFile(
        join(root, ".codeflowmu", "runtime.lock"),
        JSON.stringify({ data_root: runtimeData }),
        "utf-8",
      );
      const summary = resolveReviewEvidence({
        projectRoot: root,
        task_id: TASK,
        root_task_id: "TASK-20260810-030",
        thread_key: "thread-31",
        session_id: SESSION,
        run_id: "run-31",
        attempt_id: ATTEMPT,
        lease_id: LEASE,
        agent_id: "DEV-01",
        role: "DEV",
      });
      assert.equal(summary.session.found, true);
      assert.equal(summary.execution_provenance?.hard_sources_agree, 3);
      assert.equal(
        summary.execution_provenance?.sources.find((source) => source.source === "session_store")?.found,
        true,
      );
    } finally {
      await rm(root, { recursive: true, force: true }).catch(() => undefined);
      await rm(runtimeData, { recursive: true, force: true }).catch(() => undefined);
    }
  });

  it("hard-fails a real REPORT/attempt binding conflict", async () => {
    const root = await mkdtemp(join(tmpdir(), "codeflowmu-provenance-conflict-"));
    try {
      await writeCoreEvidence(root);
      const summary = resolveReviewEvidence({
        projectRoot: root,
        task_id: TASK,
        session_id: SESSION,
        run_id: "run-31",
        attempt_id: "attempt-other",
        lease_id: LEASE,
        agent_id: "DEV-01",
        role: "DEV",
      });
      assert.equal(summary.execution_provenance?.status, "conflict");
      assert.ok(summary.execution_provenance?.conflicts.some((item) => item.field === "attempt_id"));
      assert.ok(summary.warnings.some((warning) => warning.startsWith("binding_conflict:")));
    } finally {
      await rm(root, { recursive: true, force: true }).catch(() => undefined);
    }
  });

  it("does not treat only two durable sources as complete execution proof", async () => {
    const root = await mkdtemp(join(tmpdir(), "codeflowmu-provenance-missing-runtime-"));
    try {
      await writeCoreEvidence(root);
      await rm(join(root, "fcop", "logs", "runtime"), {
        recursive: true,
        force: true,
      });
      const summary = resolveReviewEvidence({
        projectRoot: root,
        task_id: TASK,
        root_task_id: "TASK-20260810-030",
        thread_key: "thread-31",
        session_id: SESSION,
        run_id: "run-31",
        attempt_id: ATTEMPT,
        lease_id: LEASE,
        agent_id: "DEV-01",
        role: "DEV",
      });
      assert.equal(summary.execution_provenance?.status, "unavailable");
      assert.equal(summary.execution_provenance?.hard_sources_agree, 2);
      assert.equal(summary.session.found, false);
      assert.ok(
        summary.execution_provenance?.observability_gaps.includes("runtime_jsonl_absent"),
      );
    } finally {
      await rm(root, { recursive: true, force: true }).catch(() => undefined);
    }
  });
});
