import { describe, expect, it } from "vitest";
import {
  I1LifecycleService,
  type ApplicationTransaction,
  type ExecutionDispatchRequest,
  type ExecutionDispatcher,
  type UnitOfWork,
} from "@awp/application";
import {
  capability,
  unsafeOpaqueId,
  type CorrelationId,
  type OperationId,
  type PlanId,
  type PlanRevisionId,
  type PrincipalId,
  type ProjectId,
  type TaskId,
} from "@awp/contracts";
import type { FactoryRun, Plan, PlanRevision, Project, Task } from "@awp/domain";

describe("I1 FactoryRun orchestration identity", () => {
  it("creates one FactoryRun for the approved PlanRevision and dispatches every dependency-legal root under it", async () => {
    const project: Project = {
      id: unsafeOpaqueId<ProjectId>("project-orchestration"),
      name: "Orchestration",
      repositoryUrl: "https://example.test/awp.git",
      status: "active",
      revision: 1,
    };
    let plan: Plan = {
      id: unsafeOpaqueId<PlanId>("plan-orchestration"),
      projectId: project.id,
      title: "One run",
      status: "draft",
      revision: 1,
    };
    const revision: PlanRevision = {
      id: unsafeOpaqueId<PlanRevisionId>("plan-revision-orchestration"),
      planId: plan.id,
      projectId: project.id,
      sequence: 1,
      title: plan.title,
      goalIds: [],
    };
    let tasks: Task[] = [
      {
        id: unsafeOpaqueId<TaskId>("task-root-a"),
        projectId: project.id,
        planRevisionId: revision.id,
        title: "Root A",
        position: 0,
        status: "planned",
        dependencyIds: [],
        goalIds: [],
        revision: 1,
      },
      {
        id: unsafeOpaqueId<TaskId>("task-root-b"),
        projectId: project.id,
        planRevisionId: revision.id,
        title: "Root B",
        position: 1,
        status: "planned",
        dependencyIds: [],
        goalIds: [],
        revision: 1,
      },
      {
        id: unsafeOpaqueId<TaskId>("task-join"),
        projectId: project.id,
        planRevisionId: revision.id,
        title: "Join",
        position: 2,
        status: "planned",
        dependencyIds: [
          unsafeOpaqueId<TaskId>("task-root-a"),
          unsafeOpaqueId<TaskId>("task-root-b"),
        ],
        goalIds: [],
        revision: 1,
      },
    ];
    const factoryRuns: FactoryRun[] = [];
    const dispatches: ExecutionDispatchRequest[] = [];
    const events: Array<{ type?: string; aggregateId?: string; payload?: unknown }> = [];
    const dispatcher: ExecutionDispatcher = {
      async dispatch(request) {
        dispatches.push(request);
      },
    };
    const tx = {
      projects: { getById: async () => project },
      plans: {
        getById: async () => plan,
        update: async (value: Plan) => void (plan = value),
      },
      planRevisions: { getCurrent: async () => revision },
      tasks: {
        listByProject: async () => tasks,
        update: async (value: Task) => {
          tasks = tasks.map((candidate) => (candidate.id === value.id ? value : candidate));
        },
      },
      factoryRuns: {
        listByProject: async () => factoryRuns,
        insert: async (value: FactoryRun) => void factoryRuns.push(value),
      },
      events: {
        append: async (event: { type?: string; aggregateId?: string; payload?: unknown }) =>
          void events.push(event),
      },
      audit: { append: async () => undefined },
      outbox: { append: async () => undefined },
    } as unknown as ApplicationTransaction;
    const uow: UnitOfWork = { transaction: async (work) => work(tx) };
    let generated = 0;
    const lifecycle = new I1LifecycleService(
      uow,
      { next: () => unsafeOpaqueId(`generated-${++generated}`) },
      { now: () => new Date("2026-08-22T00:00:00Z") },
      dispatcher,
    );
    const context = {
      operationId: unsafeOpaqueId<OperationId>("operation-approve"),
      correlationId: unsafeOpaqueId<CorrelationId>("correlation-approve"),
      idempotencyKey: "approve:one-run",
      authority: {
        principal: {
          id: unsafeOpaqueId<PrincipalId>("owner"),
          kind: "human" as const,
          capabilities: [capability("plan.approve")],
        },
        capabilities: [capability("plan.approve")],
        projectId: project.id,
      },
    };

    await lifecycle.approvePlan(project.id, plan.id, context, {
      accountId: "codex-owner",
      model: "gpt-owner",
    });

    expect(factoryRuns).toHaveLength(1);
    expect(factoryRuns[0]).toMatchObject({
      projectId: project.id,
      planRevisionId: revision.id,
      status: "queued",
    });
    expect(factoryRuns[0]).not.toHaveProperty("taskId");
    expect(factoryRuns[0]).toMatchObject({ accountId: "codex-owner", model: "gpt-owner" });
    expect(dispatches).toHaveLength(2);
    expect(new Set(dispatches.map((request) => request.factoryRun.id))).toEqual(
      new Set([factoryRuns[0]!.id]),
    );
    expect(dispatches.map((request) => request.task.id)).toEqual([tasks[0]!.id, tasks[1]!.id]);
    expect(dispatches.every((request) => request.selection?.accountId === "codex-owner")).toBe(
      true,
    );
    expect(tasks.map((task) => task.status)).toEqual(["dispatched", "dispatched", "blocked"]);
    expect(events.filter((event) => event.type === "TaskDispatched")).toEqual([
      expect.objectContaining({ aggregateId: tasks[0]!.id }),
      expect.objectContaining({ aggregateId: tasks[1]!.id }),
    ]);
    expect(events.filter((event) => event.type === "FactoryRunQueued")).toHaveLength(1);

    await lifecycle.approvePlan(project.id, plan.id, context, {
      accountId: "codex-owner",
      model: "gpt-owner",
    });
    expect(factoryRuns).toHaveLength(1);
    expect(dispatches).toHaveLength(4);
    expect(new Set(dispatches.map((request) => request.factoryRun.id))).toEqual(
      new Set([factoryRuns[0]!.id]),
    );

    await expect(
      lifecycle.approvePlan(project.id, plan.id, context, {
        accountId: "different-account",
        model: "gpt-owner",
      }),
    ).rejects.toThrow(/cannot change FactoryRun account provenance/);
  });
});
