import { describe, it, expect } from "vitest";
import {
  findBestMatchingRule,
  applyExperienceMultipliers,
  applyExperienceMultipliersBatch,
  type ExperienceMultiplierRule,
  type RateAdjustmentInput,
} from "../estimate/experience-multiplier.js";

// ─── Fixtures ────────────────────────────────────────────────────────────────

const globalRule: ExperienceMultiplierRule = {
  costMultiplier: 1.1,
  billMultiplier: 1.1,
  description: "Global 10% uplift",
};

const chapterRule: ExperienceMultiplierRule = {
  chapter: "Animation",
  costMultiplier: 1.2,
  billMultiplier: 1.3,
};

const locationRule: ExperienceMultiplierRule = {
  location: "India",
  costMultiplier: 0.6,
  billMultiplier: 0.8,
};

const levelRule: ExperienceMultiplierRule = {
  level: "Senior",
  costMultiplier: 1.5,
  billMultiplier: 1.6,
};

const chapterLocationRule: ExperienceMultiplierRule = {
  chapter: "Animation",
  location: "India",
  costMultiplier: 0.7,
  billMultiplier: 0.85,
  shoringRatio: 0.5,
  additionalEffortRatio: 0.2,
};

const chapterLevelRule: ExperienceMultiplierRule = {
  chapter: "Animation",
  level: "Senior",
  costMultiplier: 1.4,
  billMultiplier: 1.5,
};

const locationLevelRule: ExperienceMultiplierRule = {
  location: "India",
  level: "Senior",
  costMultiplier: 0.8,
  billMultiplier: 0.9,
};

const exactRule: ExperienceMultiplierRule = {
  chapter: "Animation",
  location: "India",
  level: "Senior",
  costMultiplier: 0.9,
  billMultiplier: 1.0,
  shoringRatio: 0.3,
  additionalEffortRatio: 0.1,
  description: "Exact match rule",
};

const allRules: ExperienceMultiplierRule[] = [
  globalRule,
  chapterRule,
  locationRule,
  levelRule,
  chapterLocationRule,
  chapterLevelRule,
  locationLevelRule,
  exactRule,
];

// ─── findBestMatchingRule ────────────────────────────────────────────────────

describe("findBestMatchingRule", () => {
  it("returns null when no rules are provided", () => {
    expect(findBestMatchingRule({ chapter: "X" }, [])).toBeNull();
  });

  it("returns null when no rule matches", () => {
    const rules: ExperienceMultiplierRule[] = [
      { chapter: "Compositing", costMultiplier: 1, billMultiplier: 1 },
    ];
    expect(findBestMatchingRule({ chapter: "Animation" }, rules)).toBeNull();
  });

  it("matches exact chapter+location+level (specificity 7)", () => {
    const result = findBestMatchingRule(
      { chapter: "Animation", location: "India", level: "Senior" },
      allRules,
    );
    expect(result).toBe(exactRule);
  });

  it("matches chapter+location (specificity 6) when level does not match exact", () => {
    const result = findBestMatchingRule(
      { chapter: "Animation", location: "India", level: "Junior" },
      allRules,
    );
    expect(result).toBe(chapterLocationRule);
  });

  it("matches chapter+level (specificity 5) when location differs", () => {
    const result = findBestMatchingRule(
      { chapter: "Animation", location: "Germany", level: "Senior" },
      allRules,
    );
    expect(result).toBe(chapterLevelRule);
  });

  it("matches chapter only (specificity 4)", () => {
    const result = findBestMatchingRule(
      { chapter: "Animation", location: "Germany", level: "Junior" },
      allRules,
    );
    expect(result).toBe(chapterRule);
  });

  it("matches location+level (specificity 3)", () => {
    const result = findBestMatchingRule(
      { chapter: "Compositing", location: "India", level: "Senior" },
      allRules,
    );
    expect(result).toBe(locationLevelRule);
  });

  it("matches location only (specificity 2)", () => {
    const result = findBestMatchingRule(
      { chapter: "Compositing", location: "India", level: "Junior" },
      allRules,
    );
    expect(result).toBe(locationRule);
  });

  it("matches level only (specificity 1)", () => {
    const result = findBestMatchingRule(
      { chapter: "Compositing", location: "Germany", level: "Senior" },
      allRules,
    );
    expect(result).toBe(levelRule);
  });

  it("falls back to global rule (specificity 0)", () => {
    const result = findBestMatchingRule(
      { chapter: "Compositing", location: "Germany", level: "Junior" },
      allRules,
    );
    expect(result).toBe(globalRule);
  });

  it("is case-insensitive when matching", () => {
    const rules: ExperienceMultiplierRule[] = [
      { chapter: "ANIMATION", location: "india", costMultiplier: 1, billMultiplier: 1 },
    ];
    const result = findBestMatchingRule(
      { chapter: "animation", location: "India" },
      rules,
    );
    expect(result).toBe(rules[0]);
  });

  it("treats null / undefined input fields as wildcard-matchable", () => {
    const result = findBestMatchingRule({}, [globalRule]);
    expect(result).toBe(globalRule);
  });
});

// ─── applyExperienceMultipliers ─────────────────────────────────────────────

describe("applyExperienceMultipliers", () => {
  const baseInput: RateAdjustmentInput = {
    costRateCents: 10000,
    billRateCents: 15000,
    hours: 100,
    chapter: "Animation",
    location: "India",
    level: "Senior",
  };

  it("returns unchanged values when no rules provided", () => {
    const result = applyExperienceMultipliers(baseInput, []);
    expect(result.adjustedCostRateCents).toBe(10000);
    expect(result.adjustedBillRateCents).toBe(15000);
    expect(result.adjustedHours).toBe(100);
    expect(result.appliedRules).toHaveLength(1);
    expect(result.appliedRules[0]).toContain("No rules provided");
  });

  it("returns unchanged values when no rule matches", () => {
    const rules: ExperienceMultiplierRule[] = [
      { chapter: "Compositing", costMultiplier: 2, billMultiplier: 2 },
    ];
    const result = applyExperienceMultipliers(
      { ...baseInput, chapter: "Animation" },
      rules,
    );
    expect(result.adjustedCostRateCents).toBe(10000);
    expect(result.adjustedBillRateCents).toBe(15000);
  });

  it("applies rate multipliers correctly and rounds to integer cents", () => {
    const rules: ExperienceMultiplierRule[] = [
      { chapter: "Animation", costMultiplier: 1.15, billMultiplier: 1.33 },
    ];
    const result = applyExperienceMultipliers(
      { costRateCents: 10000, billRateCents: 15000, hours: 80, chapter: "Animation" },
      rules,
    );
    expect(result.adjustedCostRateCents).toBe(11500); // 10000 * 1.15
    expect(result.adjustedBillRateCents).toBe(19950); // 15000 * 1.33
    expect(result.adjustedHours).toBe(80); // no shoring
  });

  it("handles rounding edge case (e.g. 3333 * 1.1 = 3666.3 => 3666)", () => {
    const rules: ExperienceMultiplierRule[] = [
      { costMultiplier: 1.1, billMultiplier: 1.0 },
    ];
    const result = applyExperienceMultipliers(
      { costRateCents: 3333, billRateCents: 5000, hours: 10 },
      rules,
    );
    expect(result.adjustedCostRateCents).toBe(3666); // Math.round(3666.3)
  });

  it("applies shoring ratio to hours", () => {
    const rules: ExperienceMultiplierRule[] = [
      {
        costMultiplier: 1.0,
        billMultiplier: 1.0,
        shoringRatio: 0.5,
        additionalEffortRatio: 0,
      },
    ];
    const result = applyExperienceMultipliers(
      { costRateCents: 10000, billRateCents: 15000, hours: 100 },
      rules,
    );
    // 50 onsite + 50 offshore * (1 + 0) = 100 — no additional effort means hours unchanged
    expect(result.adjustedHours).toBe(100);
  });

  it("applies shoring ratio with additional effort factor", () => {
    const rules: ExperienceMultiplierRule[] = [
      {
        costMultiplier: 1.0,
        billMultiplier: 1.0,
        shoringRatio: 0.5,
        additionalEffortRatio: 0.2,
      },
    ];
    const result = applyExperienceMultipliers(
      { costRateCents: 10000, billRateCents: 15000, hours: 100 },
      rules,
    );
    // onsite = 100 * 0.5 = 50, offshore = 100 * 0.5 * 1.2 = 60 => total = 110
    expect(result.adjustedHours).toBe(110);
  });

  it("applies both rate multipliers and shoring together", () => {
    const result = applyExperienceMultipliers(baseInput, allRules);
    // Should match exactRule (chapter=Animation, location=India, level=Senior)
    expect(result.adjustedCostRateCents).toBe(Math.round(10000 * 0.9)); // 9000
    expect(result.adjustedBillRateCents).toBe(Math.round(15000 * 1.0)); // 15000
    // shoring: 0.3, additional: 0.1
    // onsite = 100 * 0.7 = 70, offshore = 100 * 0.3 * 1.1 = 33 => 103
    expect(result.adjustedHours).toBe(103);
    expect(result.appliedRules.length).toBeGreaterThanOrEqual(2);
  });

  it("includes rule description in audit trail when present", () => {
    const result = applyExperienceMultipliers(baseInput, allRules);
    const descRule = result.appliedRules.find((r) => r.includes("Exact match rule"));
    expect(descRule).toBeDefined();
  });

  it("reports no-op when multipliers are 1.0 and no shoring", () => {
    const rules: ExperienceMultiplierRule[] = [
      { costMultiplier: 1.0, billMultiplier: 1.0 },
    ];
    const result = applyExperienceMultipliers(
      { costRateCents: 5000, billRateCents: 8000, hours: 40 },
      rules,
    );
    expect(result.adjustedCostRateCents).toBe(5000);
    expect(result.adjustedBillRateCents).toBe(8000);
    expect(result.adjustedHours).toBe(40);
    expect(result.appliedRules[0]).toContain("unchanged");
  });
});

// ─── applyExperienceMultipliersBatch ────────────────────────────────────────

describe("applyExperienceMultipliersBatch", () => {
  it("processes multiple inputs and returns correct summary", () => {
    const rules: ExperienceMultiplierRule[] = [
      { chapter: "Animation", costMultiplier: 1.2, billMultiplier: 1.3 },
      { costMultiplier: 1.0, billMultiplier: 1.0 }, // global fallback
    ];

    const inputs: RateAdjustmentInput[] = [
      { costRateCents: 10000, billRateCents: 15000, hours: 50, chapter: "Animation" },
      { costRateCents: 8000, billRateCents: 12000, hours: 30, chapter: "Compositing" },
    ];

    const batch = applyExperienceMultipliersBatch(inputs, rules);
    expect(batch.results).toHaveLength(2);
    expect(batch.totalOriginalHours).toBe(80);
    expect(batch.totalAdjustedHours).toBe(80);
    // First line adjusted (cost * 1.2), second not (1.0)
    expect(batch.linesAdjusted).toBe(1);
    expect(batch.results[0]!.adjustedCostRateCents).toBe(12000);
    expect(batch.results[1]!.adjustedCostRateCents).toBe(8000);
  });

  it("handles empty input array", () => {
    const batch = applyExperienceMultipliersBatch([], [globalRule]);
    expect(batch.results).toHaveLength(0);
    expect(batch.totalOriginalHours).toBe(0);
    expect(batch.totalAdjustedHours).toBe(0);
    expect(batch.linesAdjusted).toBe(0);
  });
});
