import { describe, expect, it } from "vitest";
import {
  AutoMergeReconciliationWorker,
  DurableAutoMergeDispatcher,
  DurableExecutionDispatcher,
  I1_DURABLE_AUTOMERGE_WORKFLOW_KIND,
  I1_DURABLE_DISPATCH_WORKFLOW_KIND,
  ProviderExecutionWorker,
  type ApplicationTransaction,
  type DurableWorkflowProvider,
  type ExecutionDispatchRequest,
  type ExecutionDispatcher,
  type FactoryProvider,
  type UnitOfWork,
} from "@awp/application";
import {
  authorityContext,
  unsafeOpaqueId,
  type ChangeSetId,
  type ConnectionId,
  type CorrelationId,
  type CredentialReferenceId,
  type FactoryRunId,
  type OperationId,
  type PlanRevisionId,
  type PrincipalId,
  type ProjectId,
  type TaskId,
} from "@awp/contracts";
import type { FactoryRun, Task } from "@awp/domain";

const projectId = unsafeOpaqueId<ProjectId>("project-provider-execution");
const factoryRun: FactoryRun = {
  id: unsafeOpaqueId<FactoryRunId>("factory-provider-execution"),
  projectId,
  planRevisionId: unsafeOpaqueId<PlanRevisionId>("revision-provider-execution"),
  status: "queued",
  revision: 1,
};
const task: Task = {
  id: unsafeOpaqueId<TaskId>("task-provider-execution"),
  projectId,
  planRevisionId: factoryRun.planRevisionId,
  title: "Provider-backed execution",
  status: "dispatched",
  dependencyIds: [],
  revision: 1,
};
const context = {
  operationId: unsafeOpaqueId<OperationId>("operation-provider-execution"),
  correlationId: unsafeOpaqueId<CorrelationId>("correlation-provider-execution"),
  idempotencyKey: "provider-execution",
  authority: authorityContext(
    {
      id: unsafeOpaqueId<PrincipalId>("principal:owner"),
      kind: "human",
      capabilities: [],
    },
    [],
    projectId,
  ),
};

describe("provider-backed durable execution", () => {
  it("starts Fabro before delegating the Task to workspace execution", async () => {
    const calls: string[] = [];
    const tx = {
      factoryRuns: { getById: async () => factoryRun },
      tasks: { getById: async () => task },
    } as unknown as ApplicationTransaction;
    const uow: UnitOfWork = { transaction: async (work) => work(tx) };
    const factoryProvider = {
      async start(providerContext, id, revisionId) {
        calls.push("factory");
        expect(providerContext.idempotencyKey).toBe(`factory:${factoryRun.id}`);
        expect(id).toBe(factoryRun.id);
        expect(revisionId).toBe(factoryRun.planRevisionId);
        return {
          value: { state: "running", details: {} },
          references: [],
          observedAt: "2026-08-22T00:00:00.000Z",
        };
      },
    } as FactoryProvider;
    const downstream: ExecutionDispatcher = {
      async dispatch(request) {
        calls.push("workspace");
        expect(request.factoryRun.id).toBe(factoryRun.id);
        expect(request.task.id).toBe(task.id);
        expect(request.selection?.accountId).toBe("codex-owner");
      },
    };
    const worker = new ProviderExecutionWorker(uow, factoryProvider, downstream, {
      factoryConnectionId: unsafeOpaqueId<ConnectionId>("connection:fabro"),
      factoryCredentialReferenceId: unsafeOpaqueId<CredentialReferenceId>(
        "credential:fabro-control",
      ),
    });

    const result = await worker.execute({
      factoryRunId: factoryRun.id,
      taskId: task.id,
      context,
      selection: { accountId: "codex-owner", model: "gpt-owner" },
    });

    expect(calls).toEqual(["factory", "workspace"]);
    expect(result).toMatchObject({
      factoryRunId: factoryRun.id,
      taskId: task.id,
      factoryState: "running",
    });
  });

  it("maps each FactoryRun/Task pair to one deterministic DBOS workflow operation", async () => {
    const starts: Array<{ operationId: string; workflowKind: string; input: unknown }> = [];
    const workflowProvider = {
      async start(_providerContext, operationId, workflowKind, input) {
        starts.push({ operationId, workflowKind, input });
        return {
          value: { state: "running", details: {} },
          references: [],
          observedAt: "2026-08-22T00:00:00.000Z",
        };
      },
    } as DurableWorkflowProvider;
    const dispatcher = new DurableExecutionDispatcher(workflowProvider, {
      workflowConnectionId: unsafeOpaqueId<ConnectionId>("connection:dbos"),
      workflowCredentialReferenceId:
        unsafeOpaqueId<CredentialReferenceId>("credential:dbos-control"),
    });
    const request: ExecutionDispatchRequest = {
      factoryRun,
      task,
      context,
      selection: { accountId: "codex-owner" },
    };

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

    expect(starts).toHaveLength(2);
    expect(new Set(starts.map((start) => start.operationId))).toEqual(
      new Set([`dispatch:${factoryRun.id}:${task.id}`]),
    );
    expect(starts[0]?.workflowKind).toBe(I1_DURABLE_DISPATCH_WORKFLOW_KIND);
    expect(starts[0]?.input).toMatchObject({
      factoryRunId: factoryRun.id,
      taskId: task.id,
      selection: { accountId: "codex-owner" },
    });
  });

  it("maps one ChangeSet to one deterministic durable auto-merge workflow identity", async () => {
    const starts: Array<{ operationId: string; workflowKind: string; input: unknown }> = [];
    const workflowProvider = {
      async start(_providerContext, operationId, workflowKind, input) {
        starts.push({ operationId, workflowKind, input });
        return {
          value: { state: "running", details: {} },
          references: [],
          observedAt: "2026-08-23T00:00:00.000Z",
        };
      },
    } as DurableWorkflowProvider;
    const dispatcher = new DurableAutoMergeDispatcher(workflowProvider, {
      workflowConnectionId: unsafeOpaqueId<ConnectionId>("connection:dbos"),
      workflowCredentialReferenceId:
        unsafeOpaqueId<CredentialReferenceId>("credential:dbos-control"),
    });
    const changeSetId = unsafeOpaqueId<ChangeSetId>("changeset-provider-automerge");

    await dispatcher.schedule(changeSetId, context);
    await dispatcher.schedule(changeSetId, context);

    expect(starts).toHaveLength(2);
    expect(new Set(starts.map((start) => start.operationId))).toEqual(
      new Set([`automerge:${changeSetId}`]),
    );
    expect(starts[0]?.workflowKind).toBe(I1_DURABLE_AUTOMERGE_WORKFLOW_KIND);
    expect(starts[0]?.input).toMatchObject({ changeSetId, context });
  });

  it("adapts durable auto-merge workflow input back to the AWP ChangeSet reconciler", async () => {
    const changeSetId = unsafeOpaqueId<ChangeSetId>("changeset-reconcile-worker");
    const calls: string[] = [];
    const worker = new AutoMergeReconciliationWorker({
      async reconcileAutoMerge(id, reconcileContext) {
        calls.push(String(id));
        expect(id).toBe(changeSetId);
        expect(reconcileContext).toBe(context);
        return { state: "waiting", changeSetId: id, reason: "checks pending" };
      },
    });

    const result = await worker.execute({ changeSetId, context });
    expect(calls).toEqual([changeSetId]);
    expect(result).toEqual({ state: "waiting", changeSetId, reason: "checks pending" });
  });
});
