/**
 * PR-37 Delivery Planning — Capacity Planner (Part I)
 *
 * Calculates total effort (with complexity factors), total capacity,
 * capacity utilization, risk-adjusted effort, delivery forecast dates,
 * and resource recommendations when utilization exceeds 100%.
 *
 * @module pr-37-delivery-planning/capacity-planner
 * @since PR-37
 */

/**
 * Default capacity planning configuration.
 *
 * @param {object} [cfg]
 * @param {number} [cfg.teamSize=5] — Number of team members
 * @param {number} [cfg.velocity=20] — Team velocity in story points per sprint
 * @param {number} [cfg.availabilityFactor=0.8] — Team availability (80% = 1 day lost/week)
 * @param {object} [cfg.complexityFactors] — Story point multiplier by complexity
 * @param {number} [cfg.complexityFactors.high=2.0]
 * @param {number} [cfg.complexityFactors.medium=1.0]
 * @param {number} [cfg.complexityFactors.low=0.5]
 * @param {number} [cfg.riskBufferPercent=20] — Risk buffer as percentage of effort
 * @returns {object} Normalized config
 */
export function defaultCapacityConfig(cfg = {}) {
  return {
    teamSize: cfg.teamSize ?? 5,
    velocity: cfg.velocity ?? 20,
    availabilityFactor: cfg.availabilityFactor ?? 0.8,
    complexityFactors: {
      high: (cfg.complexityFactors && cfg.complexityFactors.high) ?? 2.0,
      medium: (cfg.complexityFactors && cfg.complexityFactors.medium) ?? 1.0,
      low: (cfg.complexityFactors && cfg.complexityFactors.low) ?? 0.5,
    },
    riskBufferPercent: cfg.riskBufferPercent ?? 20,
  };
}

/**
 * Generate a comprehensive capacity plan from sprint and release plans.
 *
 * @param {object} opts
 * @param {object} opts.sprints — Sprint plan { sprints, metrics }
 * @param {object} opts.releases — Release plan { releases }
 * @param {object} [opts.config] — Capacity planning config
 * @returns {object} Capacity plan
 *
 * @example
 * ```js
 * const plan = generateCapacityPlan({
 *   sprints: sprintPlan,
 *   releases: releasePlan,
 *   config: { teamSize: 5, velocity: 20, availabilityFactor: 0.8, riskBufferPercent: 20 },
 * });
 * ```
 */
export function generateCapacityPlan({ sprints, releases, config }) {
  const cfg = defaultCapacityConfig(config);
  const warnings = [];

  // ── 1. Validate inputs ──
  // Accept both { sprints: [...] } (array) and { sprints: { sprints: [...] } } (plan object)
  const sprintList = (sprints && (Array.isArray(sprints) ? sprints : sprints.sprints)) || [];
  const releaseList = (releases && (Array.isArray(releases) ? releases : releases.releases)) || [];
  const totalSprints = sprintList.length;

  if (totalSprints === 0) {
    return {
      totalEffort: 0,
      totalCapacity: 0,
      capacityUtilization: "0.0%",
      sprintBySprint: [],
      riskAdjustedEffort: 0,
      deliveryForecast: { optimistic: "N/A", realistic: "N/A", pessimistic: "N/A" },
      resourceRecommendations: ["No sprints to plan; provide a sprint plan first"],
      warnings: ["No sprints available for capacity planning"],
    };
  }

  // ── 2. Calculate total effort with complexity factors ──
  // Each sprint's total story points serve as effort, adjusted by complexity
  let totalRawEffort = 0;
  const sprintEffortMap = new Map();

  for (const sprint of sprintList) {
    const rawPoints = sprint.totalStoryPoints || 0;
    totalRawEffort += rawPoints;
    sprintEffortMap.set(sprint.sprintNumber, rawPoints);
  }

  // Estimate complexity distribution (in real scenarios, stories would have complexity tags)
  // Default heuristic: 20% high, 60% medium, 20% low
  const highRatio = 0.2;
  const mediumRatio = 0.6;
  const lowRatio = 0.2;

  const highEffort = totalRawEffort * highRatio * cfg.complexityFactors.high;
  const mediumEffort = totalRawEffort * mediumRatio * cfg.complexityFactors.medium;
  const lowEffort = totalRawEffort * lowRatio * cfg.complexityFactors.low;

  const totalEffort = Math.round(highEffort + mediumEffort + lowEffort);

  // ── 3. Calculate total capacity ──
  const totalCapacity = Math.round(
    cfg.teamSize * cfg.velocity * totalSprints * cfg.availabilityFactor
  );

  // ── 4. Sprint-by-sprint capacity vs allocation ──
  const sprintBySprint = sprintList.map((sprint) => {
    const sprintCapacity = Math.round(cfg.velocity * cfg.availabilityFactor);
    const allocated = sprintEffortMap.get(sprint.sprintNumber) || 0;
    const utilization = sprintCapacity > 0
      ? ((allocated / sprintCapacity) * 100).toFixed(1) + "%"
      : "0.0%";

    return {
      sprintNumber: sprint.sprintNumber,
      sprintGoal: sprint.sprintGoal || `Sprint ${sprint.sprintNumber}`,
      capacity: sprintCapacity,
      allocated,
      utilization,
      overCapacity: allocated > sprintCapacity,
    };
  });

  // ── 5. Capacity utilization ──
  const capacityUtilization = totalCapacity > 0
    ? ((totalEffort / totalCapacity) * 100).toFixed(1) + "%"
    : "0.0%";

  // ── 6. Risk-adjusted effort ──
  const riskBuffer = totalEffort * (cfg.riskBufferPercent / 100);
  const riskAdjustedEffort = Math.round(totalEffort + riskBuffer);

  // ── 7. Delivery forecast ──
  const deliveryForecast = computeDeliveryForecast(
    sprintList, totalEffort, totalCapacity, cfg, releaseList
  );

  // ── 8. Resource recommendations ──
  const resourceRecommendations = computeResourceRecommendations(
    totalEffort, totalCapacity, riskAdjustedEffort, cfg, totalSprints
  );

  // ── 9. Warnings ──
  const utilizationNum = totalCapacity > 0 ? (totalEffort / totalCapacity) * 100 : 0;
  if (utilizationNum > 100) {
    warnings.push(`Overall capacity utilization is ${utilizationNum.toFixed(1)}% — team is over capacity`);
  }
  for (const ss of sprintBySprint) {
    if (ss.overCapacity) {
      warnings.push(`Sprint ${ss.sprintNumber} is over capacity (${ss.allocated} > ${ss.capacity} pts)`);
    }
  }

  return {
    totalEffort,
    totalCapacity,
    capacityUtilization,
    sprintBySprint,
    riskAdjustedEffort,
    deliveryForecast,
    resourceRecommendations,
    warnings,
  };
}

/**
 * Compute optimistic, realistic, and pessimistic delivery dates.
 *
 * @param {object[]} sprintList — Sprints from sprint plan
 * @param {number} totalEffort — Adjusted total effort
 * @param {number} totalCapacity — Calculated total capacity
 * @param {object} cfg — Capacity config
 * @param {object[]} releaseList — Releases from release plan
 * @returns {object} { optimistic: string, realistic: string, pessimistic: string }
 */
function computeDeliveryForecast(sprintList, totalEffort, totalCapacity, cfg, releaseList) {
  const sprintCount = sprintList.length;
  if (sprintCount === 0) {
    return { optimistic: "N/A", realistic: "N/A", pessimistic: "N/A" };
  }

  const weeksPerSprint = 2; // Standard 2-week sprints

  // Optimistic: current velocity continues, no blockers
  const optimisticWeeks = Math.ceil(
    (totalEffort / Math.max(1, cfg.velocity)) * weeksPerSprint
  );

  // Realistic: with availability factor
  const realisticWeeks = Math.ceil(
    (totalEffort / Math.max(1, cfg.velocity * cfg.availabilityFactor)) * weeksPerSprint
  );

  // Pessimistic: risk-adjusted effort / lower availability
  const pessimisticEffort = totalEffort * 1.3; // 30% buffer for worst case
  const pessimisticWeeks = Math.ceil(
    (pessimisticEffort / Math.max(1, cfg.velocity * cfg.availabilityFactor * 0.85)) * weeksPerSprint
  );

  // Reference: last major release determines baseline
  const lastRelease = releaseList.length > 0
    ? releaseList[releaseList.length - 1]
    : null;
  const releaseLabel = lastRelease
    ? `after ${lastRelease.name} start`
    : "from project start";

  return {
    optimistic: `Week ${optimisticWeeks} ${releaseLabel}`,
    realistic: `Week ${realisticWeeks} ${releaseLabel}`,
    pessimistic: `Week ${pessimisticWeeks} ${releaseLabel}`,
  };
}

/**
 * Compute resource recommendations when capacity is exceeded.
 *
 * @param {number} totalEffort — Adjusted total effort
 * @param {number} totalCapacity — Total capacity
 * @param {number} riskAdjustedEffort — Effort with risk buffer
 * @param {object} cfg — Capacity config
 * @param {number} totalSprints — Number of sprints
 * @returns {string[]} Recommendations
 */
function computeResourceRecommendations(totalEffort, totalCapacity, riskAdjustedEffort, cfg, totalSprints) {
  const recommendations = [];

  const utilizationNum = totalCapacity > 0 ? (totalEffort / totalCapacity) * 100 : 0;

  if (utilizationNum <= 100) {
    recommendations.push("Team capacity is sufficient for planned work");
    recommendations.push(`Keep team size at ${cfg.teamSize} with current velocity of ${cfg.velocity} pts/sprint`);
    return recommendations;
  }

  // Over-capacity: suggest fixes
  const excessPct = utilizationNum - 100;
  const requiredSprintIncrease = Math.ceil(
    (riskAdjustedEffort / Math.max(1, cfg.velocity * cfg.availabilityFactor)) - totalSprints
  );

  const requiredTeamSize = Math.ceil(
    riskAdjustedEffort / (totalSprints * cfg.velocity * cfg.availabilityFactor)
  );

  if (requiredSprintIncrease > 0) {
    recommendations.push(
      `Extend by ${requiredSprintIncrease} sprint(s) to accommodate ${riskAdjustedEffort} risk-adjusted points`
    );
  }

  if (requiredTeamSize > cfg.teamSize) {
    recommendations.push(
      `Increase team size from ${cfg.teamSize} to ${requiredTeamSize} to stay within ${totalSprints} sprints`
    );
  }

  if (excessPct > 50) {
    recommendations.push(
      "Consider reducing scope or deferring non-critical features to later releases"
    );
    recommendations.push(
      `Explore outsourcing ${Math.ceil(totalEffort * 0.2)} story points to external teams`
    );
  }

  recommendations.push(
    `Reduce complexity factor application by simplifying high-complexity stories`
  );

  return recommendations;
}

export default { generateCapacityPlan, defaultCapacityConfig };
