/**
 * Agent Runtime Layer — Execution Pipeline
 *
 * PR-35 Part C：统一执行链路。
 *
 *  Pipeline:
 *    Task → Planning → Execution → Checklist → Baseline → Gate → Certification → Artifacts → Reporting
 *
 *  要求：
 *    - 所有任务必须经过 Checklist / Baseline / Gate / Certification
 *    - 不得绕过任何质量门禁
 *    - 每阶段产出 Observation + Metric
 *
 *  纯函数引擎。执行器通过依赖注入传入。
 *
 * @module agent-runtime/pipeline
 */

import {
  TASK_STATE, STEP_STATE, PIPELINE_STAGE, RISK_LEVEL,
  createTaskResult, createRisk, createObservation, createMetric,
  createGateDecision, createCertificationResult, GATE_DECISION, CERTIFICATION_TIER,
} from "./domain.mjs";

import {
  transition, areStepDependenciesMet, getStepsSummary,
  isAllStepsDone, hasFailedSteps, computeRecoveryStrategy, retryTask,
} from "./state-machine.mjs";

import { autoGateAdapter } from "./adapters.mjs";

// ═══════════════════════════════════════════════════════
// 主入口
// ═══════════════════════════════════════════════════════

/**
 * 执行完整 Pipeline。
 *
 * @param {object} task       — Task 对象
 * @param {object} runtimeCtx — RuntimeContext (由 createRuntimeContext 创建)
 * @param {object} [opts]
 * @param {function} [opts.stepExecutor] — (step, task, ctx) => ExecutionResult
 * @returns {object} TaskResult
 */
export function executePipeline(task, runtimeCtx, opts = {}) {
  const startTime = Date.now();
  const observations = [];
  const metrics = [];
  const risks = [];

  // ── 0. 输入验证 ──
  if (!task) return _failResult("No task provided", "N/A", startTime);
  if (!runtimeCtx) return _failResult("No runtime context provided", task.id, startTime);
  if (!task.executionPlan || !Array.isArray(task.executionPlan.steps) || task.executionPlan.steps.length === 0) {
    return _failResult("Task has no execution plan or steps", task.id, startTime);
  }

  const pipelineOpts = { ...runtimeCtx.pipelineOptions, ...opts };
  const stepExecutor = pipelineOpts.stepExecutor || _defaultStepExecutor;

  // ── 1. Planning (plan → queued) ──
  try {
    _advance(task, TASK_STATE.PLANNED);
    observations.push(createObservation({ taskId: task.id, stage: PIPELINE_STAGE.PLANNING, message: "Execution plan accepted." }));
    _advance(task, TASK_STATE.QUEUED);
  } catch (e) {
    return _failResult(`Planning failed: ${e.message}`, task.id, startTime, observations);
  }

  // ── 2. Execution ──
  _advance(task, TASK_STATE.RUNNING);
  observations.push(createObservation({ taskId: task.id, stage: PIPELINE_STAGE.EXECUTION, message: "Step execution started." }));

  const stepResults = [];
  const changedFiles = new Set();
  const artifactIds = [];

  for (const step of task.executionPlan.steps) {
    // 依赖检查
    if (!areStepDependenciesMet(step, task.executionPlan.steps)) {
      step.state = STEP_STATE.SKIPPED;
      step.completedAt = new Date().toISOString();
      observations.push(createObservation({
        taskId: task.id, stage: PIPELINE_STAGE.EXECUTION,
        message: `Step "${step.name}" skipped: dependencies not met.`,
        severity: "warn",
      }));
      stepResults.push({ stepId: step.id, status: "skipped", reason: "dependencies-not-met" });
      continue;
    }

    // 执行
    step.state = STEP_STATE.RUNNING;
    step.startedAt = new Date().toISOString();

    try {
      const result = stepExecutor(step, task, runtimeCtx);
      step.state = STEP_STATE.COMPLETED;
      step.completedAt = new Date().toISOString();
      step.result = result;
      step.durationMs = result.durationMs || 0;

      stepResults.push({
        stepId: step.id,
        status: "completed",
        changedFiles: result.changedFiles || [],
        artifactIds: result.artifactIds || [],
        output: result.output,
      });

      if (Array.isArray(result.changedFiles)) result.changedFiles.forEach(f => changedFiles.add(f));
      if (Array.isArray(result.artifactIds)) result.artifactIds.forEach(id => artifactIds.push(id));
      if (Array.isArray(result.risks)) risks.push(...result.risks);
    } catch (err) {
      step.state = STEP_STATE.FAILED;
      step.completedAt = new Date().toISOString();
      step.error = err.message;

      stepResults.push({ stepId: step.id, status: "failed", error: err.message });
      observations.push(createObservation({
        taskId: task.id, stage: PIPELINE_STAGE.EXECUTION,
        message: `Step "${step.name}" failed: ${err.message}`,
        severity: "error",
      }));

      if (pipelineOpts.stopOnStepFailure) break;
    }
  }

  // 步骤失败 → 提前退出
  if (hasFailedSteps(task.executionPlan.steps) && pipelineOpts.stopOnStepFailure) {
    const summary = getStepsSummary(task.executionPlan.steps);
    try { _advance(task, TASK_STATE.FAILED); } catch (_) {}
    return _buildResult(task, {
      stepResults, changedFiles: [...changedFiles].sort(), artifactIds,
      gateDecision: createGateDecision({ decision: GATE_DECISION.CLOSED, blockers: [`${summary.failed}/${summary.total} steps failed.`], summary: "Step execution failed — pipeline aborted." }),
      certification: createCertificationResult({ tier: CERTIFICATION_TIER.NONE, score: 0, failedCriteria: ["all-steps-must-pass"] }),
      risks, observations, metrics, startTime,
      recommendedNextAction: _repairAdvice(summary),
    });
  }

  // ── 3. Quality Gates (强制) ──

  // 3a. Checklist ─────────────────────────────────────
  _advance(task, TASK_STATE.CHECKING);
  observations.push(createObservation({ taskId: task.id, stage: PIPELINE_STAGE.CHECKLIST, message: "Checklist evaluation started." }));

  let checklistResult = null;
  if (runtimeCtx.checklistAdapter) {
    try {
      checklistResult = runtimeCtx.checklistAdapter({
        task, steps: task.executionPlan.steps, stepResults,
        changedFiles: [...changedFiles],
      });
      observations.push(createObservation({
        taskId: task.id, stage: PIPELINE_STAGE.CHECKLIST,
        message: `Checklist result: ${checklistResult.status} (confidence: ${checklistResult.confidence})`,
        severity: checklistResult.status === "fail" ? "error" : checklistResult.status === "warn" ? "warn" : "info",
      }));
      if (checklistResult.blockers?.length) {
        checklistResult.blockers.forEach(b => risks.push(createRisk({ description: String(b), level: RISK_LEVEL.HIGH, source: "checklist" })));
      }
    } catch (e) {
      checklistResult = { status: "fail", blockers: [`Checklist error: ${e.message}`], warnings: [] };
      risks.push(createRisk({ description: `Checklist evaluation failed: ${e.message}`, level: RISK_LEVEL.HIGH, source: "checklist" }));
    }
  } else {
    observations.push(createObservation({ taskId: task.id, stage: PIPELINE_STAGE.CHECKLIST, message: "No checklist adapter configured.", severity: "warn" }));
  }
  metrics.push(createMetric({ taskId: task.id, name: "checklist.confidence", value: checklistResult?.confidence ?? 0, unit: "pct" }));

  // 3b. Baseline ─────────────────────────────────────
  observations.push(createObservation({ taskId: task.id, stage: PIPELINE_STAGE.BASELINE_COMPARISON, message: "Baseline comparison started." }));

  let baselineComparison = null;
  if (runtimeCtx.baselineAdapter) {
    try {
      baselineComparison = runtimeCtx.baselineAdapter(
        { task },
        runtimeCtx.baselineData,
        runtimeCtx.candidateData || _buildCandidateFromTask(task, [...changedFiles]),
      );
      observations.push(createObservation({
        taskId: task.id, stage: PIPELINE_STAGE.BASELINE_COMPARISON,
        message: `Baseline result: ${baselineComparison.status} (${baselineComparison.changes?.length || 0} changes)`,
        severity: baselineComparison.status === "fail" ? "error" : baselineComparison.status === "warn" ? "warn" : "info",
      }));
    } catch (e) {
      baselineComparison = { status: "fail", blockers: [`Baseline error: ${e.message}`], changes: [], warnings: [] };
      risks.push(createRisk({ description: `Baseline comparison failed: ${e.message}`, level: RISK_LEVEL.HIGH, source: "baseline" }));
    }
  }

  // 3c. Gate ─────────────────────────────────────────
  observations.push(createObservation({ taskId: task.id, stage: PIPELINE_STAGE.GATE_EVALUATION, message: "Gate evaluation started." }));

  let gateDecision;
  const gateFn = runtimeCtx.gateAdapter || autoGateAdapter;
  try {
    gateDecision = gateFn(runtimeCtx, { checklist: checklistResult, baselineCompare: baselineComparison });
    observations.push(createObservation({
      taskId: task.id, stage: PIPELINE_STAGE.GATE_EVALUATION,
      message: `Gate decision: ${gateDecision.decision} (${gateDecision.blockers.length} blockers, ${gateDecision.warnings.length} warnings)`,
      severity: gateDecision.decision === GATE_DECISION.CLOSED ? "error" : gateDecision.decision === GATE_DECISION.WARN ? "warn" : "info",
    }));
  } catch (e) {
    gateDecision = createGateDecision({ decision: GATE_DECISION.CLOSED, blockers: [`Gate error: ${e.message}`] });
  }

  // 3d. Certification ────────────────────────────────
  observations.push(createObservation({ taskId: task.id, stage: PIPELINE_STAGE.CERTIFICATION, message: "Certification evaluation started." }));

  let certification;
  if (runtimeCtx.certificationAdapter) {
    try {
      certification = runtimeCtx.certificationAdapter({
        task, steps: task.executionPlan.steps, stepResults,
        checklist: checklistResult, baselineCompare: baselineComparison,
        gateDecision, risks,
      });
      observations.push(createObservation({
        taskId: task.id, stage: PIPELINE_STAGE.CERTIFICATION,
        message: `Certification: ${certification.tier.toUpperCase()} (score: ${certification.score})`,
        severity: certification.tier === CERTIFICATION_TIER.NONE ? "error" : "info",
      }));
    } catch (e) {
      certification = createCertificationResult({ tier: CERTIFICATION_TIER.NONE, score: 0, failedCriteria: [`Certification error: ${e.message}`] });
    }
  } else {
    certification = createCertificationResult({ tier: CERTIFICATION_TIER.NONE, score: 0, failedCriteria: ["no-certification-adapter"] });
  }
  metrics.push(createMetric({ taskId: task.id, name: "certification.score", value: certification.score, unit: "pct" }));

  // ── 4. Final State ──
  let finalStatus;
  if (gateDecision.decision === GATE_DECISION.CLOSED) {
    const hasFailBlocker = gateDecision.blockers.some(b => /fail/i.test(String(b)));
    finalStatus = hasFailBlocker ? TASK_STATE.FAILED : TASK_STATE.BLOCKED;
  } else {
    finalStatus = TASK_STATE.PASSED;
  }
  try { _advance(task, finalStatus); } catch (_) {}

  // ── 5. Build Result ──
  return _buildResult(task, {
    stepResults, changedFiles: [...changedFiles].sort(), artifactIds,
    checklistResult, baselineComparison, gateDecision, certification,
    risks, observations, metrics, startTime,
    recommendedNextAction: _nextAction(gateDecision, certification, task),
  });
}

// ═══════════════════════════════════════════════════════
// 恢复执行（retry）
// ═══════════════════════════════════════════════════════

/**
 * 对失败任务执行恢复。
 * @param {object} task
 * @param {object} runtimeCtx
 * @param {object} [opts]
 * @returns {object} TaskResult
 */
export function recoverTask(task, runtimeCtx, opts = {}) {
  const strategy = computeRecoveryStrategy(task);
  if (!strategy.canRetry) {
    return _failResult(strategy.reason, task.id, Date.now());
  }
  try {
    retryTask(task);
  } catch (e) {
    return _failResult(`Recovery failed: ${e.message}`, task.id, Date.now());
  }
  return executePipeline(task, runtimeCtx, opts);
}

// ═══════════════════════════════════════════════════════
// 内部工具
// ═══════════════════════════════════════════════════════

function _advance(task, targetState) {
  try {
    transition(task, targetState);
  } catch (e) {
    // 如果已经在目标状态或后续状态，忽略
    if (!e.message.includes("Illegal")) throw e;
  }
}

function _defaultStepExecutor(step) {
  return {
    stepId: step.id,
    status: "completed",
    changedFiles: step.expectedArtifacts || [],
    artifactIds: [],
    output: `Step "${step.name}" executed (simulated).`,
    durationMs: 0,
  };
}

function _buildCandidateFromTask(task, changedFiles) {
  return {
    blocCount: task.executionPlan?.steps?.length || 0,
    cycleCount: 1,
    candidateCount: changedFiles.length,
    activeCandidateCount: changedFiles.length,
    archivedCandidateCount: 0,
  };
}

function _buildResult(task, { stepResults, changedFiles, artifactIds, checklistResult, baselineComparison, gateDecision, certification, risks, observations, metrics, startTime, recommendedNextAction }) {
  return createTaskResult({
    taskId: task.id,
    status: task.state,
    executedSteps: stepResults,
    changedFiles,
    artifactIds,
    checklistResult,
    baselineComparison,
    gateDecision,
    certification,
    risks,
    observations,
    metrics,
    durationMs: Date.now() - startTime,
    recommendedNextAction,
  });
}

function _failResult(reason, taskId, startTime, observations = []) {
  return createTaskResult({
    taskId,
    status: TASK_STATE.FAILED,
    executedSteps: [],
    changedFiles: [],
    artifactIds: [],
    gateDecision: createGateDecision({ decision: GATE_DECISION.CLOSED, blockers: [reason], summary: `Pipeline failed: ${reason}` }),
    certification: createCertificationResult({ tier: CERTIFICATION_TIER.NONE, score: 0, failedCriteria: [reason] }),
    risks: [createRisk({ description: reason, level: RISK_LEVEL.CRITICAL, source: "pipeline" })],
    observations,
    metrics: [],
    durationMs: Date.now() - startTime,
    recommendedNextAction: `Fix the following issue and retry: ${reason}`,
  });
}

function _repairAdvice(summary) {
  return `Fix ${summary.failed} failed step(s) and re-run. Steps can be retried up to the configured max retries.`;
}

function _nextAction(gateDecision, certification, task) {
  if (gateDecision.decision === GATE_DECISION.OPEN) {
    const tier = certification?.tier || "none";
    if (tier === "platinum" || tier === "gold") return "All quality gates passed with high certification. Ready for merge/deploy.";
    if (tier === "silver") return "Quality gates passed. Address warnings before production deployment.";
    return "Quality gates passed. Improve certification tier before release.";
  }
  if (gateDecision.decision === GATE_DECISION.CLOSED) {
    if (task.state === TASK_STATE.FAILED) {
      const recovery = computeRecoveryStrategy(task);
      return recovery.canRetry
        ? `Task failed. ${recovery.reason} Fix blockers and retry.`
        : `Task failed. ${recovery.reason}`;
    }
    return "Gate is CLOSED. Review blockers and unblock to proceed.";
  }
  return "Review task output and determine next action.";
}
