import { BestAgentError, createRandomId } from "@best-agent/foundation";
import type {
  ChildTaskRecord,
  Item,
  ThreadEvidenceRecord,
  Turn,
  TurnId,
  VerdictRecord,
  WorkflowName,
  WorkflowPhaseName,
} from "@best-agent/protocol";
import type { AgentProfile } from "../agent/profile-registry.js";
import { resolveReadyWorkflowPhases, resolveWorkflowPhaseLayers } from "../core/workflow-graph.js";
import type { ConversationMessage } from "../provider/model-provider.js";

type WorkflowPhaseDefinition = {
  name: WorkflowPhaseName;
  profile: AgentProfile;
  messages: (prompt: string, phaseOutputs: Map<WorkflowPhaseName, string>) => ConversationMessage[];
};

type WorkflowDefinition = {
  summaryPrompt: string;
  steps: Array<{
    phases: WorkflowPhaseDefinition[];
    strategy: "parallel" | "sequential";
  }>;
};

type WorkflowPhaseResult = {
  childTask: ChildTaskRecord;
  childThreadId: string;
  correlationId: string;
  output: string;
  phaseName: WorkflowPhaseName;
  phaseProfile: AgentProfile;
};

type WorkflowExecutorDeps = {
  appendUnsatisfiedRootVerdict(input: {
    graphId: string;
    summary: string;
    turn: Turn;
  }): Promise<void>;
  appendSatisfiedRootVerdict(input: {
    graphId: string;
    joinedPhaseCount: number;
    supportEvidenceIds: string[];
    turn: Turn;
    workflowName: WorkflowName;
  }): Promise<void>;
  bindWorkflowRoot(turnId: TurnId, graphId: string): Promise<void>;
  buildWorkflowPhaseMessages(
    turn: Turn,
    workflowName: WorkflowName,
    phase: WorkflowPhaseDefinition,
    prompt: string,
  ): Promise<ConversationMessage[]>;
  executeWorkflowFinalSynthesis(input: {
    activeSkillIds: string[];
    hostProfile: AgentProfile;
    turn: Turn;
    workflow: WorkflowDefinition;
    workflowName: WorkflowName;
  }): Promise<{
    joinedEvidence: ThreadEvidenceRecord[];
    output: string;
  }>;
  executeWorkflowPhase(
    turn: Turn,
    hostProfile: AgentProfile,
    phaseProfile: AgentProfile,
    activeSkillIds: string[],
    phaseName: WorkflowPhaseName,
    extraMessages: ConversationMessage[],
    workflowName: WorkflowName,
  ): Promise<WorkflowPhaseResult>;
  executeWorkflowPhaseGroup(
    turn: Turn,
    hostProfile: AgentProfile,
    activeSkillIds: string[],
    phases: WorkflowPhaseDefinition[],
    prompt: string,
    workflowName: WorkflowName,
  ): Promise<Array<[WorkflowPhaseName, string]>>;
  getLatestWorkflowPhaseVerdict(
    turn: Turn,
    workflowName: WorkflowName,
    phaseName: WorkflowPhaseName,
  ): Promise<{ childTask: ChildTaskRecord; verdict: VerdictRecord } | undefined>;
  isTurnTerminatedError(error: unknown): boolean;
  listWorkflowPhaseChildTaskRecords(
    turn: Turn,
    workflowName: WorkflowName,
    phaseName: WorkflowPhaseName,
  ): Promise<ChildTaskRecord[]>;
  maxAdvancedPhaseReopens: number;
  pushItems(items: Item[]): Promise<void>;
  requireSatisfiedRootVerdict(input: { fallbackMessage: string; turn: Turn }): Promise<void>;
  resolveWorkflowDefinition(workflowName: WorkflowName): WorkflowDefinition;
  verdictClosureMode: "heuristic" | "kernel";
};

export class WorkflowExecutor {
  readonly #deps: WorkflowExecutorDeps;

  constructor(deps: WorkflowExecutorDeps) {
    this.#deps = deps;
  }

  async execute(input: {
    activeSkillIds: string[];
    hostProfile: AgentProfile;
    prompt: string;
    turn: Turn;
    workflowName: WorkflowName;
  }): Promise<string> {
    const workflow = this.#deps.resolveWorkflowDefinition(input.workflowName);
    const workflowPhases = workflow.steps.flatMap((step) => step.phases);
    const phasesByName = new Map(workflowPhases.map((phase) => [phase.name, phase] as const));
    const workflowLayers = resolveWorkflowPhaseLayers(input.workflowName);
    const pendingPhases = new Set<WorkflowPhaseName>(workflowPhases.map((phase) => phase.name));
    const completedPhases = new Set<WorkflowPhaseName>();
    const buildPhase = workflowPhases.find((phase) => phase.name === "build");
    const verifyPhase = workflowPhases.find((phase) => phase.name === "verify");
    const workflowGraphId = `${input.turn.id}:workflow:${input.workflowName}`;

    await this.#deps.bindWorkflowRoot(input.turn.id, workflowGraphId);
    await this.#deps.pushItems([
      {
        id: createRandomId<"ItemId">("item"),
        threadId: input.turn.threadId,
        turnId: input.turn.id,
        createdAt: new Date().toISOString(),
        kind: "reasoning",
        status: "draft",
        summary: `The orchestrator profile started the ${input.workflowName} workflow.`,
      },
    ]);

    try {
      while (pendingPhases.size > 0) {
        const readyPhaseNames = resolveReadyWorkflowPhases({
          completedPhases,
          pendingPhases,
          workflowLayers,
        });
        if (readyPhaseNames.length === 0) {
          throw new BestAgentError(
            "WORKFLOW_INVALID",
            `Workflow ${input.workflowName} has no ready phases to execute.`,
          );
        }
        const readyPhases = readyPhaseNames
          .map((phaseName) => phasesByName.get(phaseName))
          .filter(Boolean) as WorkflowPhaseDefinition[];
        if (readyPhases.length > 1) {
          await this.#deps.executeWorkflowPhaseGroup(
            input.turn,
            input.hostProfile,
            input.activeSkillIds,
            readyPhases,
            input.prompt,
            input.workflowName,
          );
        } else {
          const [phase] = readyPhases;
          const phaseMessages = await this.#deps.buildWorkflowPhaseMessages(
            input.turn,
            input.workflowName,
            phase,
            input.prompt,
          );
          await this.#deps.executeWorkflowPhase(
            input.turn,
            input.hostProfile,
            phase.profile,
            input.activeSkillIds,
            phase.name,
            phaseMessages,
            input.workflowName,
          );
        }
        for (const phaseName of readyPhaseNames) {
          pendingPhases.delete(phaseName);
          completedPhases.add(phaseName);
        }
      }

      while (buildPhase && verifyPhase && this.#deps.verdictClosureMode === "kernel") {
        const latestVerifyResult = await this.#deps.getLatestWorkflowPhaseVerdict(
          input.turn,
          input.workflowName,
          "verify",
        );
        if (!latestVerifyResult) {
          break;
        }
        const verifyVerdict = latestVerifyResult.verdict;
        const reopenCount = Math.max(
          0,
          (
            await this.#deps.listWorkflowPhaseChildTaskRecords(
              input.turn,
              input.workflowName,
              "verify",
            )
          ).length - 1,
        );
        if (
          verifyVerdict.status === "satisfied" ||
          reopenCount >= this.#deps.maxAdvancedPhaseReopens
        ) {
          break;
        }
        const buildMessages = await this.#deps.buildWorkflowPhaseMessages(
          input.turn,
          input.workflowName,
          buildPhase,
          input.prompt,
        );
        const lastBuildMessage = buildMessages.at(-1);
        const verdictDetail = [
          verifyVerdict.summary,
          verifyVerdict.blockingReason ? `Blocking reason: ${verifyVerdict.blockingReason}` : undefined,
        ]
          .filter((line): line is string => Boolean(line))
          .join("\n");
        const reopenedBuildMessages =
          lastBuildMessage?.role === "user"
            ? [
                ...buildMessages.slice(0, -1),
                {
                  role: "user" as const,
                  content: [
                    "Previous workflow verify verdict was unsatisfied:",
                    verdictDetail,
                    "",
                    lastBuildMessage.content,
                    "",
                    "Reopen the build phase and address the verification failure before handing back to verify.",
                  ].join("\n"),
                },
              ]
            : buildMessages;

        await this.#deps.executeWorkflowPhase(
          input.turn,
          input.hostProfile,
          buildPhase.profile,
          input.activeSkillIds,
          buildPhase.name,
          reopenedBuildMessages,
          input.workflowName,
        );

        await this.#deps.executeWorkflowPhase(
          input.turn,
          input.hostProfile,
          verifyPhase.profile,
          input.activeSkillIds,
          verifyPhase.name,
          await this.#deps.buildWorkflowPhaseMessages(
            input.turn,
            input.workflowName,
            verifyPhase,
            input.prompt,
          ),
          input.workflowName,
        );
      }

      const latestVerifyResult = verifyPhase
        ? await this.#deps.getLatestWorkflowPhaseVerdict(input.turn, input.workflowName, "verify")
        : undefined;
      if (
        this.#deps.verdictClosureMode === "kernel" &&
        latestVerifyResult &&
        latestVerifyResult.verdict.status !== "satisfied"
      ) {
        throw new BestAgentError("WORKFLOW_VERIFY_UNSATISFIED", latestVerifyResult.verdict.summary);
      }

      const synthesis = await this.#deps.executeWorkflowFinalSynthesis({
        activeSkillIds: input.activeSkillIds,
        hostProfile: input.hostProfile,
        turn: input.turn,
        workflow,
        workflowName: input.workflowName,
      });
      await this.#deps.bindWorkflowRoot(input.turn.id, workflowGraphId);
      if (latestVerifyResult && latestVerifyResult.verdict.status !== "satisfied") {
        await this.#deps.appendUnsatisfiedRootVerdict({
          graphId: workflowGraphId,
          summary: latestVerifyResult.verdict.summary,
          turn: input.turn,
        });
      } else {
        await this.#deps.appendSatisfiedRootVerdict({
          graphId: workflowGraphId,
          joinedPhaseCount: synthesis.joinedEvidence.length,
          supportEvidenceIds: synthesis.joinedEvidence.map((record) => record.evidenceId),
          turn: input.turn,
          workflowName: input.workflowName,
        });
        if (this.#deps.verdictClosureMode === "kernel") {
          await this.#deps.requireSatisfiedRootVerdict({
            fallbackMessage: `Workflow ${input.workflowName} failed to satisfy the root contract.`,
            turn: input.turn,
          });
        }
      }
      if (
        this.#deps.verdictClosureMode === "heuristic" &&
        latestVerifyResult &&
        latestVerifyResult.verdict.status !== "satisfied"
      ) {
        await this.#deps.pushItems([
          {
            id: createRandomId<"ItemId">("item"),
            threadId: input.turn.threadId,
            turnId: input.turn.id,
            createdAt: new Date().toISOString(),
            kind: "error",
            message: `Workflow verify phase unsatisfied: ${latestVerifyResult.verdict.summary}`,
            errorCode: "WORKFLOW_VERIFY_UNSATISFIED",
            recoverability: "blocked",
            scope: "turn",
          },
        ]);
        return [
          `Workflow verify phase unsatisfied: ${latestVerifyResult.verdict.summary}`,
          "The following synthesis may not meet the workflow quality bar.",
          "",
          synthesis.output,
        ].join("\n");
      }
      return synthesis.output;
    } catch (error) {
      if (!this.#deps.isTurnTerminatedError(error)) {
        await this.#deps.bindWorkflowRoot(input.turn.id, workflowGraphId);
        await this.#deps.appendUnsatisfiedRootVerdict({
          graphId: workflowGraphId,
          summary:
            error instanceof Error ? error.message : `Workflow ${input.workflowName} failed.`,
          turn: input.turn,
        });
      }
      throw error;
    }
  }
}
