/**
 * PR-37 Part J — Roadmap Alignment Engine
 *
 * 验证 SF-01 Roadmap 与 PR-37 交付规划（Sprint Plan / Release Plan / Batch Plan）的一致性。
 *
 * 检查：
 *   - Phase coverage: Roadmap 的阶段是否被 Release Plan 完整覆盖
 *   - Story coverage: MVP/V1/V2 的 Story 是否被 Sprint Plan 完整分配
 *   - Dependency integrity: 关键依赖链是否在 Release 和 Batch 中被正确处理
 *   - Budget alignment: 每个 Phase 的预算是否与实际工作量（Story Points）匹配
 *   - Timeline consistency: Sprint Plan 的总周数是否在 Roadmap 的 Phase 时间范围内
 *
 * @module pr-37-delivery-planning/roadmap-aligner
 * @since PR-37
 */

/**
 * @typedef {object} RoadmapPhase
 * @property {string} name — Phase name (mvp / v1 / v2 / enterprise / global)
 * @property {string} scope — Phase description
 * @property {number} duration — Duration in months
 * @property {number} budget — Budget in currency units
 */

/**
 * @typedef {object} AlignmentReport
 * @property {string} overallVerdict — "aligned" / "misaligned" / "partial"
 * @property {number} alignmentScore — 0-100
 * @property {object} phaseCoverage — Per-phase coverage assessment
 * @property {object} timelineCheck — Sprint vs Roadmap timeline comparison
 * @property {object} budgetCheck — Budget vs Effort comparison
 * @property {object} dependencyCheck — Critical dependency path verification
 * @property {string[]} warnings — Alignment warnings
 * @property {string[]} recommendations — Actionable recommendations
 */

// ══════════════════════════════════════════════════════════
// Main Alignment Function
// ══════════════════════════════════════════════════════════

/**
 * Align SF-01 Roadmap with PR-37 delivery plans.
 *
 * @param {object} opts
 * @param {object} opts.roadmap — SF-01 Roadmap (phases array)
 * @param {object} opts.sprintPlan — PR-37 Sprint Plan
 * @param {object} opts.releasePlan — PR-37 Release Plan
 * @param {object} opts.batchPlan — PR-37 Batch Plan
 * @param {object} opts.mvpScope — SF-02 MVP Scope
 * @returns {AlignmentReport}
 */
export function alignRoadmapWithPlans({ roadmap, sprintPlan, releasePlan, batchPlan, mvpScope }) {
  const warnings = [];
  const recommendations = [];
  let alignmentScore = 100;

  // ── 1. Phase Coverage Check ──
  const phaseCoverage = checkPhaseCoverage(roadmap, releasePlan, mvpScope);
  if (!phaseCoverage.allCovered) {
    alignmentScore -= 15;
    warnings.push(...phaseCoverage.warnings);
    recommendations.push("Ensure all roadmap phases have corresponding releases");
  }

  // ── 2. Story Coverage Check ──
  const storyCoverage = checkStoryCoverage(sprintPlan, mvpScope);
  if (storyCoverage.uncoveredCount > 0) {
    alignmentScore -= Math.min(25, storyCoverage.uncoveredCount * 2);
    warnings.push(`⚠️ ${storyCoverage.uncoveredCount} MVP/V1 stories not assigned to any Sprint`);
    recommendations.push(`Assign ${storyCoverage.uncoveredCount} unplanned stories to sprints or backlog`);
  }

  // ── 3. Timeline Consistency ──
  const timelineCheck = checkTimelineConsistency(roadmap, sprintPlan);
  if (timelineCheck.misalignment) {
    alignmentScore -= 20;
    warnings.push(timelineCheck.message);
    recommendations.push(timelineCheck.recommendation);
  }

  // ── 4. Budget Alignment ──
  const budgetCheck = checkBudgetAlignment(roadmap, sprintPlan, mvpScope);
  if (budgetCheck.misalignment) {
    alignmentScore -= 15;
    warnings.push(budgetCheck.message);
    recommendations.push(budgetCheck.recommendation);
  }

  // ── 5. Dependency Integrity ──
  const dependencyCheck = checkDependencyIntegrity(batchPlan, releasePlan);
  if (dependencyCheck.misalignment) {
    alignmentScore -= 15;
    warnings.push(...dependencyCheck.warnings);
    recommendations.push(...dependencyCheck.recommendations);
  }

  const overallVerdict = alignmentScore >= 90 ? "aligned"
    : alignmentScore >= 60 ? "partial"
    : "misaligned";

  return {
    overallVerdict,
    alignmentScore: Math.max(0, alignmentScore),
    phaseCoverage,
    storyCoverage,
    timelineCheck,
    budgetCheck,
    dependencyCheck,
    warnings,
    recommendations,
    summary: generateAlignmentSummary(overallVerdict, alignmentScore, roadmap, sprintPlan),
  };
}

// ══════════════════════════════════════════════════════════
// Phase Coverage
// ══════════════════════════════════════════════════════════

function checkPhaseCoverage(roadmap, releasePlan, mvpScope) {
  const phases = roadmap?.phases || [];
  const releases = releasePlan?.releases || [];
  const covered = [];
  const uncovered = [];
  const warnings = [];

  // Map phase names to release versions
  const phaseReleaseMap = {
    mvp: ["1.0.0"],
    v1: ["2.0.0", "1.1.0"],
    v2: ["3.0.0"],
    enterprise: ["Enterprise Edition", "3.0.0"],
    global: ["Global Edition", "4.0.0"],
  };

  for (const phase of phases) {
    const expectedReleases = phaseReleaseMap[phase.name] || [];
    const found = releases.filter(r => expectedReleases.includes(r.version));
    if (found.length > 0) {
      covered.push({ phase: phase.name, releases: found.map(r => r.version) });
    } else {
      uncovered.push({ phase: phase.name, expected: expectedReleases });
      warnings.push(`⛔ Phase "${phase.name}" has no matching release in Release Plan (expected: ${expectedReleases.join(", ")})`);
    }
  }

  // Also check that all releases map to a phase
  const allReleaseVersions = releases.map(r => r.version);
  const allExpectedVersions = Object.values(phaseReleaseMap).flat();
  const extraReleases = allReleaseVersions.filter(v => !allExpectedVersions.includes(v));
  if (extraReleases.length > 0) {
    warnings.push(`⚠️ Release Plan contains ${extraReleases.length} release(s) without roadmap phase mapping: ${extraReleases.join(", ")}`);
  }

  return {
    allCovered: uncovered.length === 0,
    covered,
    uncovered,
    extraReleases,
    warnings,
    summary: `${covered.length}/${phases.length} roadmap phases covered by Release Plan`,
  };
}

// ══════════════════════════════════════════════════════════
// Story Coverage
// ══════════════════════════════════════════════════════════

function checkStoryCoverage(sprintPlan, mvpScope) {
  const allocatedIds = new Set();
  for (const sprint of (sprintPlan?.sprints || [])) {
    for (const sid of (sprint.storyIds || [])) {
      allocatedIds.add(sid);
    }
  }

  const unallocated = new Set();
  const unplanned = sprintPlan?.unplannedStories || [];

  // MVP + V1 stories should all be allocated
  const mvpStoryIds = new Set(
    (mvpScope?.mvp?.mustHaveStories || []).map(s => s.id)
  );

  let uncoveredCount = 0;
  for (const sid of mvpStoryIds) {
    if (!allocatedIds.has(sid)) {
      unallocated.add(sid);
      uncoveredCount++;
    }
  }

  for (const sid of unplanned) {
    if (!allocatedIds.has(sid)) {
      unallocated.add(sid);
      uncoveredCount++;
    }
  }

  return {
    uncoveredCount,
    unallocatedStories: [...unallocated],
    totalAllocated: allocatedIds.size,
  };
}

// ══════════════════════════════════════════════════════════
// Timeline Consistency
// ══════════════════════════════════════════════════════════

function checkTimelineConsistency(roadmap, sprintPlan) {
  const phases = roadmap?.phases || [];
  const sprints = sprintPlan?.sprints || [];
  const totalSprintWeeks = (sprintPlan?.metrics?.estimatedDurationWeeks) || 0;

  // Roadmap total in months → weeks (approximate 4 weeks/month)
  const totalRoadmapMonths = phases.reduce((sum, p) => sum + (p.duration || p.durationMonths || 0), 0);
  const totalRoadmapWeeks = totalRoadmapMonths * 4;

  // MVP phase duration
  const mvpPhase = phases.find(p => p.name === "mvp");
  const mvpPhaseWeeks = (mvpPhase?.duration || mvpPhase?.durationMonths || 0) * 4;

  // MVP sprint weeks
  const mvpSprints = sprints.filter(s => {
    // Heuristic: first few sprints that cover the MVP points
    const running = sprints
      .slice(0, sprints.indexOf(s) + 1)
      .reduce((sum, sp) => sum + (sp.totalStoryPoints || 0), 0);
    const mvpTotal = (sprintPlan?.metrics?.totalPoints || 0) * 0.35; // ~35% of total is MVP
    return running <= mvpTotal;
  });
  const mvpSprintWeeks = mvpSprints.length * 2; // 2 weeks/sprint

  const misalignment = mvpSprintWeeks > mvpPhaseWeeks * 1.2 || totalSprintWeeks > totalRoadmapWeeks * 1.2;
  const message = misalignment
    ? `⚠️ Timeline misalignment: Sprint plan (${totalSprintWeeks}w) exceeds roadmap (${totalRoadmapWeeks}w) by ${totalSprintWeeks - totalRoadmapWeeks}w`
    : `✅ Timeline aligned: Sprint plan (${totalSprintWeeks}w) fits within roadmap (${totalRoadmapWeeks}w)`;

  const recommendation = misalignment
    ? `Consider reducing Sprint scope by ${Math.round((totalSprintWeeks - totalRoadmapWeeks) / 2)} sprints, or extend roadmap timeline by ${Math.ceil((totalSprintWeeks - totalRoadmapWeeks) / 4)} months`
    : "No action needed — timeline is consistent";

  return {
    misalignment,
    message,
    recommendation,
    sprintWeeks: totalSprintWeeks,
    roadmapWeeks: totalRoadmapWeeks,
    mvpSprintWeeks,
    mvpPhaseWeeks,
  };
}

// ══════════════════════════════════════════════════════════
// Budget Alignment
// ══════════════════════════════════════════════════════════

function checkBudgetAlignment(roadmap, sprintPlan, mvpScope) {
  const phases = roadmap?.phases || [];
  const totalBudget = phases.reduce((sum, p) => sum + (p.budget || 0), 0);
  const totalPoints = sprintPlan?.metrics?.totalPoints || 0;

  if (totalBudget === 0 || totalPoints === 0) {
    return {
      misalignment: false,
      message: "Insufficient data for budget alignment check",
      recommendation: "Ensure roadmap has budget estimates and sprint plan has story points",
      costPerPoint: 0,
    };
  }

  const costPerPoint = Math.round(totalBudget / totalPoints);
  const mvpBudget = phases.find(p => p.name === "mvp")?.budget || 0;
  const mvpPoints = mvpScope?.mvp?.totalStoryPoints || 0;
  const mvpCostAtRate = mvpPoints * costPerPoint;

  // More than 20% deviation = misalignment
  const deviation = mvpBudget > 0 ? Math.abs(mvpCostAtRate - mvpBudget) / mvpBudget : 0;
  const misalignment = deviation > 0.2;

  const message = misalignment
    ? `⚠️ Budget misalignment: MVP estimated cost at rate ($${mvpCostAtRate.toLocaleString()}) deviates ${(deviation * 100).toFixed(0)}% from roadmap budget ($${mvpBudget.toLocaleString()})`
    : `✅ Budget aligned: MVP cost at $${costPerPoint}/point (${(deviation * 100).toFixed(0)}% deviation)`;

  const recommendation = misalignment
    ? `Review story point estimates or adjust roadmap budget. Current rate: $${costPerPoint}/point, roadmap implies $${Math.round(mvpBudget / Math.max(1, mvpPoints))}/point`
    : "No action needed — budget is consistent";

  return {
    misalignment,
    message,
    recommendation,
    costPerPoint,
    mvpCostAtRate,
    mvpBudget,
    deviation: (deviation * 100).toFixed(1) + "%",
  };
}

// ══════════════════════════════════════════════════════════
// Dependency Integrity
// ══════════════════════════════════════════════════════════

function checkDependencyIntegrity(batchPlan, releasePlan) {
  const batches = batchPlan?.batches || [];
  const releases = releasePlan?.releases || [];
  const warnings = [];
  const recommendations = [];

  // Check: Development batches should feed into testing batches
  const devBatches = batches.filter(b => b.batchType === "DevelopmentBatch");
  const testBatches = batches.filter(b => b.batchType === "TestingBatch");
  const releaseBatches = batches.filter(b => b.batchType === "ReleaseBatch");
  const deliveryBatches = batches.filter(b => b.batchType === "DeliveryBatch");

  if (devBatches.length > 0 && testBatches.length === 0) {
    warnings.push("⚠️ Development batches exist but no TestingBatch defined");
    recommendations.push("Add TestingBatch(s) that consume DevelopmentBatch output");
  }

  if (releaseBatches.length === 0 && releases.length > 0) {
    warnings.push("⚠️ Releases defined but no ReleaseBatch for deployment coordination");
    recommendations.push("Add ReleaseBatch(s) to map features to deployment stages");
  }

  // Check: Release dependencies should be satisfied
  for (let i = 1; i < releases.length; i++) {
    const prevIds = new Set();
    for (let j = 0; j < i; j++) {
      (releases[j]?.features?.featureNames || []).forEach(n => prevIds.add(n));
    }
  }

  const allBatchTypes = [...new Set(batches.map(b => b.batchType))];
  const expectedTypes = ["DevelopmentBatch", "TestingBatch", "ReleaseBatch", "DeliveryBatch"];
  const missingTypes = expectedTypes.filter(t => !allBatchTypes.includes(t));

  if (missingTypes.length > 0) {
    warnings.push(`⚠️ Missing batch types: ${missingTypes.join(", ")}`);
    recommendations.push(
      "Ensure full batch pipeline: DevelopmentBatch → TestingBatch → ReleaseBatch → DeliveryBatch"
    );
  }

  return {
    misalignment: warnings.length > 0,
    warnings,
    recommendations,
    batchTypeCoverage: `${allBatchTypes.length}/${expectedTypes.length}`,
    missingTypes,
  };
}

// ══════════════════════════════════════════════════════════
// Summary
// ══════════════════════════════════════════════════════════

function generateAlignmentSummary(verdict, score, roadmap, sprintPlan) {
  const phaseCount = roadmap?.phases?.length || 0;
  const sprintCount = sprintPlan?.sprints?.length || 0;
  const totalWeeks = sprintPlan?.metrics?.estimatedDurationWeeks || 0;

  return [
    `## Roadmap Alignment: ${verdict.toUpperCase()} (${score}/100)`,
    "",
    `- **Roadmap Phases:** ${phaseCount}`,
    `- **Delivery Sprints:** ${sprintCount}`,
    `- **Estimated Duration:** ${totalWeeks} weeks`,
    verdict === "aligned"
      ? "✅ All delivery plans are consistent with the product roadmap."
      : verdict === "partial"
      ? "⚠️ Delivery plans partially align with the roadmap — see recommendations."
      : "❌ Significant misalignment between delivery plans and roadmap.",
  ].join("\n");
}
