import { describe, expect, it } from "vitest";
import {
  WorkspaceExecutionDispatcher,
  type AgentProvider,
  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 workspaceKeys: string[] = [];
    const seenLaunches: Array<{ workspaceId: WorkspaceId; agentRunId?: AgentRunId }> = [];
    const workspaceProvider = {
      async create(providerContext, workspaceId, _profile, agentRunId) {
        createCalls += 1;
        workspaceKeys.push(providerContext.idempotencyKey);
        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 agentStartKeys: string[] = [];
    let agentStartCalls = 0;
    const agentProvider = {
      async startAttempt(providerContext) {
        agentStartCalls += 1;
        agentStartKeys.push(providerContext.idempotencyKey);
        if (agentStartCalls === 1) {
          throw Object.assign(new Error("temporary ACP outage"), { status: 503 });
        }
        return {
          value: { state: "running", details: {} },
          references: [],
          observedAt: "2026-08-22T00:00:00.000Z",
        };
      },
    } as AgentProvider;

    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"),
      unsafeOpaqueId("event-4"),
      unsafeOpaqueId("audit-4"),
      unsafeOpaqueId("outbox-4"),
    ];
    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"),
        agentProvider,
        agentConnectionId: unsafeOpaqueId("connection:acp"),
        agentCredentialReferenceId: unsafeOpaqueId<CredentialReferenceId>("credential:acp"),
      },
    );
    const authority = authorityContext(
      {
        id: unsafeOpaqueId<PrincipalId>("principal:owner"),
        kind: "human",
        capabilities: [],
      },
      [],
      projectId,
    );
    const firstContext = {
      operationId: unsafeOpaqueId<OperationId>("operation-dispatch-replay-a"),
      correlationId: unsafeOpaqueId<CorrelationId>("correlation-dispatch-replay-a"),
      idempotencyKey: "transient-request-a",
      authority,
    };
    const secondContext = {
      operationId: unsafeOpaqueId<OperationId>("operation-dispatch-replay-b"),
      correlationId: unsafeOpaqueId<CorrelationId>("correlation-dispatch-replay-b"),
      idempotencyKey: "transient-request-b",
      authority,
    };

    await expect(dispatcher.dispatch({ factoryRun, task, context: firstContext })).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 expect(dispatcher.dispatch({ factoryRun, task, context: firstContext })).rejects.toThrow(
      "temporary ACP outage",
    );
    expect([...attempts.values()][0]).toMatchObject({ status: "created" });

    await dispatcher.dispatch({ factoryRun, task, context: secondContext });
    await dispatcher.dispatch({ factoryRun, task, context: secondContext });

    expect(createCalls).toBe(3);
    expect(new Set(workspaceKeys)).toEqual(
      new Set(["workspace:workspace-dispatch-replay:provision"]),
    );
    expect(agentStartCalls).toBe(2);
    expect(new Set(agentStartKeys)).toEqual(
      new Set(["attempt:attempt-dispatch-replay:agent:initial"]),
    );
    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");
  });
});
