import { describe, expect, it } from "vitest";
import {
  WorkspaceExecutionDispatcher,
  type ApplicationTransaction,
  type UnitOfWork,
  type WorkspaceProvider,
} from "@awp/application";
import {
  authorityContext,
  unsafeOpaqueId,
  type AgentRunId,
  type AttemptId,
  type CorrelationId,
  type CredentialReferenceId,
  type FactoryRunId,
  type OperationId,
  type PlanRevisionId,
  type PrincipalId,
  type ProjectId,
  type ProviderId,
  type TaskId,
  type WorkspaceId,
} from "@awp/contracts";
import type { AgentRun, Attempt, FactoryRun, Task, Workspace } from "@awp/domain";

describe("WorkspaceExecutionDispatcher replay safety", () => {
  it("reuses one Workspace/AgentRun/Attempt when provisioning is retried", async () => {
    const projectId = unsafeOpaqueId<ProjectId>("project-dispatch-replay");
    const factoryRunId = unsafeOpaqueId<FactoryRunId>("factory-dispatch-replay");
    const taskId = unsafeOpaqueId<TaskId>("task-dispatch-replay");
    const planRevisionId = unsafeOpaqueId<PlanRevisionId>("revision-dispatch-replay");
    const providerId = unsafeOpaqueId<ProviderId>("provider:acp");
    const task: Task = {
      id: taskId,
      projectId,
      planRevisionId,
      title: "Replay-safe dispatch",
      status: "dispatched",
      dependencyIds: [],
      revision: 1,
    };
    let factoryRun: FactoryRun = {
      id: factoryRunId,
      projectId,
      planRevisionId,
      status: "queued",
      revision: 1,
    };
    const workspaces = new Map<WorkspaceId, Workspace>();
    const agentRuns = new Map<AgentRunId, AgentRun>();
    const attempts = new Map<AttemptId, Attempt>();
    const tx = {
      workspaces: {
        getById: async (id: WorkspaceId) => workspaces.get(id),
        insert: async (value: Workspace) => void workspaces.set(value.id, value),
      },
      agentRuns: {
        getById: async (id: AgentRunId) => agentRuns.get(id),
        listByProject: async () => [...agentRuns.values()],
        insert: async (value: AgentRun) => void agentRuns.set(value.id, value),
        update: async (value: AgentRun) => void agentRuns.set(value.id, value),
      },
      attempts: {
        getById: async (id: AttemptId) => attempts.get(id),
        listByAgentRunIds: async (ids: readonly AgentRunId[]) =>
          [...attempts.values()].filter((attempt) => ids.includes(attempt.agentRunId)),
        insert: async (value: Attempt) => void attempts.set(value.id, value),
        update: async (value: Attempt) => void attempts.set(value.id, value),
      },
      factoryRuns: {
        getById: async () => factoryRun,
        update: async (value: FactoryRun) => void (factoryRun = value),
      },
      events: { append: async () => undefined },
      audit: { append: async () => undefined },
      outbox: { append: async () => undefined },
    } as unknown as ApplicationTransaction;
    const uow: UnitOfWork = { transaction: async (work) => work(tx) };

    let createCalls = 0;
    const seenLaunches: Array<{ workspaceId: WorkspaceId; agentRunId?: AgentRunId }> = [];
    const workspaceProvider = {
      async create(_context, workspaceId, _profile, agentRunId) {
        createCalls += 1;
        seenLaunches.push({ workspaceId, agentRunId });
        if (createCalls === 1)
          throw Object.assign(new Error("temporary Kubernetes outage"), { status: 503 });
        return {
          value: { state: "running", details: {} },
          references: [],
          observedAt: "2026-08-22T00:00:00.000Z",
        };
      },
    } as WorkspaceProvider;
    const generated = [
      unsafeOpaqueId<WorkspaceId>("workspace-dispatch-replay"),
      unsafeOpaqueId<AgentRunId>("agent-dispatch-replay"),
      unsafeOpaqueId<AttemptId>("attempt-dispatch-replay"),
      unsafeOpaqueId("event-1"),
      unsafeOpaqueId("audit-1"),
      unsafeOpaqueId("outbox-1"),
      unsafeOpaqueId("event-2"),
      unsafeOpaqueId("audit-2"),
      unsafeOpaqueId("outbox-2"),
      unsafeOpaqueId("event-3"),
      unsafeOpaqueId("audit-3"),
      unsafeOpaqueId("outbox-3"),
    ];
    let index = 0;
    const dispatcher = new WorkspaceExecutionDispatcher(
      uow,
      workspaceProvider,
      { next: () => generated[index++]! },
      { now: () => new Date("2026-08-22T00:00:00.000Z") },
      {
        profileKey: "i1",
        callbackBaseUrl: "http://control-plane",
        callbackSecret: "dispatch-secret",
        agentProviderId: providerId,
        accountId: "account-1",
        connectionId: unsafeOpaqueId("connection:kubernetes"),
        credentialReferenceId: unsafeOpaqueId<CredentialReferenceId>("credential:kubernetes"),
      },
    );
    const context = {
      operationId: unsafeOpaqueId<OperationId>("operation-dispatch-replay"),
      correlationId: unsafeOpaqueId<CorrelationId>("correlation-dispatch-replay"),
      idempotencyKey: "dispatch-replay",
      authority: authorityContext(
        {
          id: unsafeOpaqueId<PrincipalId>("principal:owner"),
          kind: "human",
          capabilities: [],
        },
        [],
        projectId,
      ),
    };
    const request = { factoryRun, task, context };

    await expect(dispatcher.dispatch(request)).rejects.toThrow("temporary Kubernetes outage");
    expect(factoryRun.status).toBe("retryable");
    expect([...agentRuns.values()][0]).toMatchObject({ status: "waiting" });
    expect([...attempts.values()][0]).toMatchObject({ status: "created" });

    await dispatcher.dispatch(request);
    await dispatcher.dispatch(request);

    expect(createCalls).toBe(2);
    expect(workspaces).toHaveLength(1);
    expect(agentRuns).toHaveLength(1);
    expect(attempts).toHaveLength(1);
    expect(new Set(seenLaunches.map((launch) => launch.workspaceId))).toHaveLength(1);
    expect(new Set(seenLaunches.map((launch) => launch.agentRunId))).toHaveLength(1);
    expect([...agentRuns.values()][0]).toMatchObject({ status: "active" });
    expect([...attempts.values()][0]).toMatchObject({ status: "running" });
    expect(factoryRun.status).toBe("running");
  });
});
