import { describe, it, expect } from "vitest";
import { computeValueScore } from "../value-scorer.js";
import type { ValueScoreWeights } from "../value-scorer.js";

const DEFAULT_WEIGHTS: ValueScoreWeights = {
  skillDepth: 0.30,
  skillBreadth: 0.15,
  costEfficiency: 0.25,
  chargeability: 0.15,
  experience: 0.15,
};

describe("computeValueScore", () => {
  it("returns all zeros for a resource with no skills", () => {
    const result = computeValueScore(
      {
        skills: [],
        lcrCents: 8000,
        chargeabilityTarget: 80,
        currentChargeability: 80,
        maxLcrCents: 15000,
      },
      DEFAULT_WEIGHTS,
    );
    expect(result.skillDepth).toBe(0);
    expect(result.skillBreadth).toBe(0);
    expect(result.experience).toBe(0);
    // chargeability should be 100 (right on target)
    expect(result.chargeability).toBe(100);
    // score must still be in 0-100
    expect(result.total).toBeGreaterThanOrEqual(0);
    expect(result.total).toBeLessThanOrEqual(100);
  });

  it("high proficiency + low cost = high total", () => {
    const result = computeValueScore(
      {
        skills: [
          { skill: "Maya", proficiency: 5, yearsExperience: 10 },
          { skill: "Houdini", proficiency: 5, yearsExperience: 8 },
          { skill: "Nuke", proficiency: 4, yearsExperience: 5 },
        ],
        lcrCents: 5000,          // low cost
        chargeabilityTarget: 80,
        currentChargeability: 80, // right on target
        maxLcrCents: 15000,
      },
      DEFAULT_WEIGHTS,
    );
    // Cheapest resource among a spread → high costEfficiency (~67%)
    // All high proficiency, on-target chargeability, experience present
    expect(result.total).toBeGreaterThanOrEqual(70);
  });

  it("weights are correctly applied to dimensions", () => {
    // Single skill, median proficiency, mid-cost, right on target, no years
    const result = computeValueScore(
      {
        skills: [{ skill: "Test", proficiency: 3 }],
        lcrCents: 7500,
        chargeabilityTarget: 80,
        currentChargeability: 80,
        maxLcrCents: 15000,
      },
      DEFAULT_WEIGHTS,
    );
    const expectedDepth = Math.round((3 / 5) * 100); // 60
    const expectedBreadth = Math.min(100, 1 * 10); // 10
    const expectedCostEff = Math.round((1 - 7500 / 15000) * 100); // 50
    const expectedChargeability = 100; // on target
    const expectedExp = 0; // no yearsExperience

    const expectedTotal = Math.round(
      expectedDepth * 0.30 +
      expectedBreadth * 0.15 +
      expectedCostEff * 0.25 +
      expectedChargeability * 0.15 +
      expectedExp * 0.15,
    );

    expect(result.skillDepth).toBe(expectedDepth);
    expect(result.skillBreadth).toBe(expectedBreadth);
    expect(result.costEfficiency).toBe(expectedCostEff);
    expect(result.chargeability).toBe(expectedChargeability);
    expect(result.experience).toBe(expectedExp);
    expect(result.total).toBe(expectedTotal);
  });

  it("guards against maxLcrCents = 0 → costEfficiency = 100", () => {
    const result = computeValueScore(
      {
        skills: [],
        lcrCents: 0,
        chargeabilityTarget: 80,
        currentChargeability: 80,
        maxLcrCents: 0, // edge case
      },
      DEFAULT_WEIGHTS,
    );
    expect(result.costEfficiency).toBe(100);
  });

  it("score is always clamped to 0-100", () => {
    const extreme = computeValueScore(
      {
        skills: Array.from({ length: 20 }, (_, i) => ({
          skill: `Skill${i}`,
          proficiency: 5 as 1 | 2 | 3 | 4 | 5,
          yearsExperience: 20,
        })),
        lcrCents: 0,
        chargeabilityTarget: 80,
        currentChargeability: 80,
        maxLcrCents: 15000,
      },
      DEFAULT_WEIGHTS,
    );
    expect(extreme.total).toBeLessThanOrEqual(100);
    expect(extreme.total).toBeGreaterThanOrEqual(0);

    const worst = computeValueScore(
      {
        skills: [],
        lcrCents: 15000,
        chargeabilityTarget: 80,
        currentChargeability: 0, // 160pp gap → chargeability = max(0, 100 - 160) = 0
        maxLcrCents: 15000,
      },
      DEFAULT_WEIGHTS,
    );
    expect(worst.total).toBe(0);
  });

  it("chargeability decreases as gap widens", () => {
    const onTarget = computeValueScore(
      { skills: [], lcrCents: 5000, chargeabilityTarget: 80, currentChargeability: 80, maxLcrCents: 5000 },
      DEFAULT_WEIGHTS,
    );
    const offTarget = computeValueScore(
      { skills: [], lcrCents: 5000, chargeabilityTarget: 80, currentChargeability: 40, maxLcrCents: 5000 },
      DEFAULT_WEIGHTS,
    );
    expect(onTarget.chargeability).toBeGreaterThan(offTarget.chargeability);
  });

  it("skill breadth caps at 100 for 10+ skills", () => {
    const manySkills = Array.from({ length: 15 }, (_, i) => ({
      skill: `Skill${i}`,
      proficiency: 3 as 1 | 2 | 3 | 4 | 5,
    }));
    const result = computeValueScore(
      { skills: manySkills, lcrCents: 5000, chargeabilityTarget: 80, currentChargeability: 80, maxLcrCents: 10000 },
      DEFAULT_WEIGHTS,
    );
    expect(result.skillBreadth).toBe(100);
  });

  it("experience caps at 100 for 10+ avg years", () => {
    const result = computeValueScore(
      {
        skills: [
          { skill: "Maya", proficiency: 4, yearsExperience: 15 },
          { skill: "Houdini", proficiency: 4, yearsExperience: 12 },
        ],
        lcrCents: 5000,
        chargeabilityTarget: 80,
        currentChargeability: 80,
        maxLcrCents: 10000,
      },
      DEFAULT_WEIGHTS,
    );
    expect(result.experience).toBe(100);
  });
});
