import { describe, expect, it } from "vitest";
import {
  computeEvenSpread,
  getEstimateMonthRange,
  rebalanceSpread,
  summarizeMonthlySpread,
} from "../estimate/monthly-spread.js";

describe("getEstimateMonthRange", () => {
  it("returns months between two dates in the same year", () => {
    expect(
      getEstimateMonthRange(new Date("2026-03-01"), new Date("2026-06-30")),
    ).toEqual(["2026-03", "2026-04", "2026-05", "2026-06"]);
  });

  it("returns a single month when start and end are in the same month", () => {
    expect(
      getEstimateMonthRange(new Date("2026-05-10"), new Date("2026-05-20")),
    ).toEqual(["2026-05"]);
  });

  it("spans across years", () => {
    expect(
      getEstimateMonthRange(new Date("2025-11-01"), new Date("2026-02-28")),
    ).toEqual(["2025-11", "2025-12", "2026-01", "2026-02"]);
  });

  it("returns empty when endDate < startDate", () => {
    expect(
      getEstimateMonthRange(new Date("2026-06-01"), new Date("2026-03-01")),
    ).toEqual([]);
  });
});

describe("computeEvenSpread", () => {
  it("distributes hours across months weighted by working days", () => {
    const result = computeEvenSpread({
      totalHours: 100,
      startDate: new Date("2026-03-01"),
      endDate: new Date("2026-05-31"),
    });

    expect(result.months).toEqual(["2026-03", "2026-04", "2026-05"]);
    // Hours should sum to exactly 100
    const sum = Object.values(result.spread).reduce((a, b) => a + b, 0);
    expect(Math.round(sum * 10) / 10).toBe(100);
    // Each month should have a positive value
    for (const month of result.months) {
      expect(result.spread[month]).toBeGreaterThan(0);
    }
  });

  it("handles a single month", () => {
    const result = computeEvenSpread({
      totalHours: 40,
      startDate: new Date("2026-04-01"),
      endDate: new Date("2026-04-30"),
    });

    expect(result.months).toEqual(["2026-04"]);
    expect(result.spread["2026-04"]).toBe(40);
  });

  it("handles partial first and last months", () => {
    const result = computeEvenSpread({
      totalHours: 80,
      startDate: new Date("2026-03-15"),
      endDate: new Date("2026-04-15"),
    });

    expect(result.months).toEqual(["2026-03", "2026-04"]);
    const sum = Object.values(result.spread).reduce((a, b) => a + b, 0);
    expect(Math.round(sum * 10) / 10).toBe(80);
    // March 15-31 has fewer working days than April 1-15
    // Both should be positive
    expect(result.spread["2026-03"]).toBeGreaterThan(0);
    expect(result.spread["2026-04"]).toBeGreaterThan(0);
  });

  it("returns empty spread for empty date range", () => {
    const result = computeEvenSpread({
      totalHours: 100,
      startDate: new Date("2026-06-01"),
      endDate: new Date("2026-03-01"),
    });

    expect(result.months).toEqual([]);
    expect(result.spread).toEqual({});
  });

  it("handles zero total hours", () => {
    const result = computeEvenSpread({
      totalHours: 0,
      startDate: new Date("2026-03-01"),
      endDate: new Date("2026-05-31"),
    });

    expect(result.months).toEqual(["2026-03", "2026-04", "2026-05"]);
    for (const month of result.months) {
      expect(result.spread[month]).toBe(0);
    }
  });
});

describe("rebalanceSpread", () => {
  it("distributes remaining hours after locked months", () => {
    const result = rebalanceSpread({
      totalHours: 100,
      startDate: new Date("2026-03-01"),
      endDate: new Date("2026-05-31"),
      lockedMonths: { "2026-03": 50 },
    });

    expect(result.months).toEqual(["2026-03", "2026-04", "2026-05"]);
    expect(result.spread["2026-03"]).toBe(50);
    // Remaining 50 hours across April + May
    const remaining = (result.spread["2026-04"] ?? 0) + (result.spread["2026-05"] ?? 0);
    expect(Math.round(remaining * 10) / 10).toBe(50);
  });

  it("handles all months locked", () => {
    const result = rebalanceSpread({
      totalHours: 100,
      startDate: new Date("2026-03-01"),
      endDate: new Date("2026-04-30"),
      lockedMonths: { "2026-03": 60, "2026-04": 40 },
    });

    expect(result.spread["2026-03"]).toBe(60);
    expect(result.spread["2026-04"]).toBe(40);
  });

  it("clamps remaining to zero when locked exceeds total", () => {
    const result = rebalanceSpread({
      totalHours: 50,
      startDate: new Date("2026-03-01"),
      endDate: new Date("2026-04-30"),
      lockedMonths: { "2026-03": 60 },
    });

    expect(result.spread["2026-03"]).toBe(60);
    // Remaining is max(0, 50-60) = 0
    expect(result.spread["2026-04"]).toBe(0);
  });

  it("ignores locked months outside the date range", () => {
    const result = rebalanceSpread({
      totalHours: 80,
      startDate: new Date("2026-03-01"),
      endDate: new Date("2026-04-30"),
      lockedMonths: { "2026-01": 30, "2026-03": 40 },
    });

    // 2026-01 is outside range, so remaining = 80 - 40 = 40
    expect(result.spread["2026-03"]).toBe(40);
    expect(result.spread["2026-04"]).toBe(40);
  });
});

describe("summarizeMonthlySpread", () => {
  it("aggregates hours per month across multiple spreads", () => {
    const result = summarizeMonthlySpread([
      { "2026-03": 30, "2026-04": 20 },
      { "2026-03": 10, "2026-04": 15, "2026-05": 25 },
    ]);

    expect(result).toEqual({
      "2026-03": 40,
      "2026-04": 35,
      "2026-05": 25,
    });
  });

  it("returns empty for empty input", () => {
    expect(summarizeMonthlySpread([])).toEqual({});
  });
});
