/**
 * Pure experience-multiplier engine.
 * Applies parametric rate adjustments (cost/bill multipliers) and
 * shoring ratios (offshore/nearshore effort factors) to demand lines.
 * No DB or IO dependencies.
 */

export interface ExperienceMultiplierRule {
  chapter?: string | null;
  location?: string | null;
  level?: string | null;
  costMultiplier: number;
  billMultiplier: number;
  shoringRatio?: number | null;
  additionalEffortRatio?: number | null;
  description?: string | null;
}

export interface RateAdjustmentInput {
  costRateCents: number;
  billRateCents: number;
  hours: number;
  chapter?: string | null;
  location?: string | null;
  level?: string | null;
}

export interface RateAdjustmentResult {
  adjustedCostRateCents: number;
  adjustedBillRateCents: number;
  adjustedHours: number;
  appliedRules: string[];
}

/**
 * Compute a specificity score for a rule given matching dimensions.
 * chapter=4, location=2, level=1 — higher means more specific.
 */
function ruleSpecificity(rule: ExperienceMultiplierRule): number {
  let score = 0;
  if (rule.chapter != null && rule.chapter !== "") score += 4;
  if (rule.location != null && rule.location !== "") score += 2;
  if (rule.level != null && rule.level !== "") score += 1;
  return score;
}

function normalise(value: string | null | undefined): string {
  return (value ?? "").toLowerCase().trim();
}

function fieldMatches(
  ruleValue: string | null | undefined,
  inputValue: string | null | undefined,
): boolean {
  const rv = normalise(ruleValue);
  if (rv === "") return true; // wildcard — matches everything
  return rv === normalise(inputValue);
}

/**
 * Find the best matching rule using hierarchical specificity.
 *
 * Matching priority (most specific first):
 *   1. chapter + location + level
 *   2. chapter + location
 *   3. chapter + level
 *   4. chapter only
 *   5. location + level
 *   6. location only
 *   7. level only
 *   8. Global fallback (no filters)
 *
 * When multiple rules share the same specificity, the first one wins.
 */
export function findBestMatchingRule(
  input: Pick<RateAdjustmentInput, "chapter" | "location" | "level">,
  rules: ExperienceMultiplierRule[],
): ExperienceMultiplierRule | null {
  let bestRule: ExperienceMultiplierRule | null = null;
  let bestScore = -1;

  for (const rule of rules) {
    const chapterMatch = fieldMatches(rule.chapter, input.chapter);
    const locationMatch = fieldMatches(rule.location, input.location);
    const levelMatch = fieldMatches(rule.level, input.level);

    if (!chapterMatch || !locationMatch || !levelMatch) continue;

    const score = ruleSpecificity(rule);
    if (score > bestScore) {
      bestScore = score;
      bestRule = rule;
    }
  }

  return bestRule;
}

/**
 * Apply experience multipliers and shoring ratios to a single rate input.
 *
 * - Matches the most specific rule by chapter/location/level.
 * - Multiplies cost and bill rates (rounds to integer cents).
 * - Adjusts hours via shoring ratio and additional effort ratio:
 *   `adjustedHours = onsiteHours + offshoreHours * (1 + additionalEffortRatio)`
 *   where `onsiteHours = hours * (1 - shoringRatio)` and `offshoreHours = hours * shoringRatio`.
 */
export function applyExperienceMultipliers(
  input: RateAdjustmentInput,
  rules: ExperienceMultiplierRule[],
): RateAdjustmentResult {
  const result: RateAdjustmentResult = {
    adjustedCostRateCents: input.costRateCents,
    adjustedBillRateCents: input.billRateCents,
    adjustedHours: input.hours,
    appliedRules: [],
  };

  if (rules.length === 0) {
    result.appliedRules.push("No rules provided — values unchanged.");
    return result;
  }

  const matched = findBestMatchingRule(input, rules);
  if (!matched) {
    result.appliedRules.push("No matching rule found — values unchanged.");
    return result;
  }

  // Describe the match for the audit trail
  const matchParts: string[] = [];
  if (matched.chapter) matchParts.push(`chapter=${matched.chapter}`);
  if (matched.location) matchParts.push(`location=${matched.location}`);
  if (matched.level) matchParts.push(`level=${matched.level}`);
  const matchLabel = matchParts.length > 0 ? matchParts.join(", ") : "global fallback";

  // Apply rate multipliers
  result.adjustedCostRateCents = Math.round(input.costRateCents * matched.costMultiplier);
  result.adjustedBillRateCents = Math.round(input.billRateCents * matched.billMultiplier);

  if (matched.costMultiplier !== 1.0 || matched.billMultiplier !== 1.0) {
    result.appliedRules.push(
      `Rate multipliers applied (${matchLabel}): cost x${matched.costMultiplier}, bill x${matched.billMultiplier}.`,
    );
  }

  // Apply shoring ratio
  const shoringRatio = matched.shoringRatio ?? 0;
  const additionalEffort = matched.additionalEffortRatio ?? 0;

  if (shoringRatio > 0) {
    const onsiteHours = input.hours * (1 - shoringRatio);
    const offshoreHours = input.hours * shoringRatio * (1 + additionalEffort);
    result.adjustedHours = Math.round((onsiteHours + offshoreHours) * 100) / 100;

    result.appliedRules.push(
      `Shoring applied (${matchLabel}): ${(shoringRatio * 100).toFixed(0)}% shored` +
        (additionalEffort > 0
          ? `, +${(additionalEffort * 100).toFixed(0)}% additional effort on shored portion`
          : "") +
        ` => ${result.adjustedHours}h (from ${input.hours}h).`,
    );
  }

  if (result.appliedRules.length === 0) {
    result.appliedRules.push(`Matched rule (${matchLabel}) — multipliers are 1.0, no shoring; values unchanged.`);
  }

  if (matched.description) {
    result.appliedRules.push(`Rule note: ${matched.description}`);
  }

  return result;
}

/**
 * Batch-apply experience multipliers to multiple demand line inputs.
 * Returns per-line results and an overall summary.
 */
export function applyExperienceMultipliersBatch(
  inputs: RateAdjustmentInput[],
  rules: ExperienceMultiplierRule[],
): {
  results: RateAdjustmentResult[];
  totalOriginalHours: number;
  totalAdjustedHours: number;
  linesAdjusted: number;
} {
  const results = inputs.map((input) => applyExperienceMultipliers(input, rules));

  let totalOriginalHours = 0;
  let totalAdjustedHours = 0;
  let linesAdjusted = 0;

  for (let i = 0; i < inputs.length; i++) {
    const input = inputs[i]!;
    const result = results[i]!;
    totalOriginalHours += input.hours;
    totalAdjustedHours += result.adjustedHours;
    if (
      result.adjustedCostRateCents !== input.costRateCents ||
      result.adjustedBillRateCents !== input.billRateCents ||
      result.adjustedHours !== input.hours
    ) {
      linesAdjusted++;
    }
  }

  return {
    results,
    totalOriginalHours: Math.round(totalOriginalHours * 100) / 100,
    totalAdjustedHours: Math.round(totalAdjustedHours * 100) / 100,
    linesAdjusted,
  };
}
