import { describe, expect, it } from "vitest";
import { calculateAllocation } from "../allocation/calculator.js";
import { DEFAULT_CALCULATION_RULES } from "../rules/default-rules.js";
import type { WeekdayAvailability, AllocationCalculationInput } from "@capakraken/shared";
import type { CalculationRule } from "@capakraken/shared";

const STD_AVAILABILITY: WeekdayAvailability = {
  monday: 8,
  tuesday: 8,
  wednesday: 8,
  thursday: 8,
  friday: 8,
  saturday: 0,
  sunday: 0,
};

const now = new Date();

function makeRule(overrides: Partial<CalculationRule>): CalculationRule {
  return {
    id: "rule_1",
    name: "Test Rule",
    description: null,
    triggerType: "SICK",
    projectId: null,
    orderType: null,
    costEffect: "ZERO",
    costReductionPercent: null,
    chargeabilityEffect: "COUNT",
    priority: 0,
    isActive: true,
    createdAt: now,
    updatedAt: now,
    ...overrides,
  };
}

describe("calculateAllocation with rules", () => {
  it("backward compatible: no rules = legacy behavior", () => {
    // Monday 2026-03-02 to Friday 2026-03-06 (5 working days)
    const result = calculateAllocation({
      lcrCents: 100,
      hoursPerDay: 8,
      startDate: new Date("2026-03-02"),
      endDate: new Date("2026-03-06"),
      availability: STD_AVAILABILITY,
    });

    expect(result.workingDays).toBe(5);
    expect(result.totalHours).toBe(40);
    expect(result.totalCostCents).toBe(4000);
    expect(result.totalChargeableHours).toBeUndefined();
    expect(result.totalProjectCostCents).toBeUndefined();
  });

  it("legacy vacation blocks the day without rules", () => {
    // Monday 2026-03-02 to Friday 2026-03-06, Tuesday is vacation
    const result = calculateAllocation({
      lcrCents: 100,
      hoursPerDay: 8,
      startDate: new Date("2026-03-02"),
      endDate: new Date("2026-03-06"),
      availability: STD_AVAILABILITY,
      vacationDates: [new Date("2026-03-03")], // Tuesday
    });

    expect(result.workingDays).toBe(4);
    expect(result.totalHours).toBe(32);
    expect(result.totalCostCents).toBe(3200);
  });

  it("sick day with rules: person chargeable, project not charged", () => {
    const sickRule = makeRule({
      triggerType: "SICK",
      costEffect: "ZERO",
      chargeabilityEffect: "COUNT",
    });

    // Monday 2026-03-02 to Friday 2026-03-06, Wednesday is sick
    const result = calculateAllocation({
      lcrCents: 100,
      hoursPerDay: 8,
      startDate: new Date("2026-03-02"),
      endDate: new Date("2026-03-06"),
      availability: STD_AVAILABILITY,
      absenceDays: [{ date: new Date("2026-03-04"), type: "SICK" }],
      calculationRules: [sickRule],
    });

    // Person worked 4 days (sick day hours = 0 effective)
    expect(result.workingDays).toBe(5); // still counted as working day
    expect(result.totalHours).toBe(32); // 4 days x 8h (sick day effective = 0)

    // Project cost: 4 days x 800 = 3200 (sick day = ZERO cost to project)
    expect(result.totalProjectCostCents).toBe(3200);

    // Chargeability: all 5 days count (8h x 5 = 40h)
    expect(result.totalChargeableHours).toBe(40);

    // Check the sick day breakdown entry (index 2 = Wednesday = 3rd day)
    const sickDay = result.dailyBreakdown[2]!;
    expect(sickDay.absenceType).toBe("SICK");
    expect(sickDay.hours).toBe(0); // not worked
    expect(sickDay.costCents).toBe(0); // not charged to project
    expect(sickDay.chargeableHours).toBe(8); // counts toward chargeability
  });

  it("vacation with rules: person chargeable, project not charged", () => {
    const vacationRule = makeRule({
      triggerType: "VACATION",
      costEffect: "ZERO",
      chargeabilityEffect: "COUNT",
    });

    // Mon-Fri, Tuesday is vacation via absenceDays
    const result = calculateAllocation({
      lcrCents: 150,
      hoursPerDay: 8,
      startDate: new Date("2026-03-02"),
      endDate: new Date("2026-03-06"),
      availability: STD_AVAILABILITY,
      absenceDays: [{ date: new Date("2026-03-03"), type: "VACATION" }],
      calculationRules: [vacationRule],
    });

    expect(result.totalHours).toBe(32);
    expect(result.totalProjectCostCents).toBe(4800); // 4 x 8 x 150
    expect(result.totalChargeableHours).toBe(40); // all 5 days
  });

  it("vacation via legacy vacationDates + rules", () => {
    const vacationRule = makeRule({
      triggerType: "VACATION",
      costEffect: "ZERO",
      chargeabilityEffect: "COUNT",
    });

    const result = calculateAllocation({
      lcrCents: 100,
      hoursPerDay: 8,
      startDate: new Date("2026-03-02"),
      endDate: new Date("2026-03-06"),
      availability: STD_AVAILABILITY,
      vacationDates: [new Date("2026-03-03")],
      calculationRules: [vacationRule],
    });

    expect(result.totalHours).toBe(32);
    expect(result.totalProjectCostCents).toBe(3200);
    expect(result.totalChargeableHours).toBe(40);
  });

  it("public holiday with rules: not chargeable, not charged", () => {
    const holidayRule = makeRule({
      triggerType: "PUBLIC_HOLIDAY",
      costEffect: "ZERO",
      chargeabilityEffect: "SKIP",
    });

    const result = calculateAllocation({
      lcrCents: 100,
      hoursPerDay: 8,
      startDate: new Date("2026-03-02"),
      endDate: new Date("2026-03-06"),
      availability: STD_AVAILABILITY,
      absenceDays: [{ date: new Date("2026-03-04"), type: "PUBLIC_HOLIDAY" }],
      calculationRules: [holidayRule],
    });

    expect(result.totalHours).toBe(32);
    expect(result.totalProjectCostCents).toBe(3200);
    // Public holiday SKIP → chargeableHours = 0 for that day
    expect(result.totalChargeableHours).toBe(32);
  });

  it("REDUCE cost effect applies percentage", () => {
    const rule = makeRule({
      triggerType: "SICK",
      costEffect: "REDUCE",
      costReductionPercent: 50,
      chargeabilityEffect: "COUNT",
    });

    const result = calculateAllocation({
      lcrCents: 100,
      hoursPerDay: 8,
      startDate: new Date("2026-03-02"),
      endDate: new Date("2026-03-06"),
      availability: STD_AVAILABILITY,
      absenceDays: [{ date: new Date("2026-03-04"), type: "SICK" }],
      calculationRules: [rule],
    });

    // Sick day: 8h x 100 cents = 800 reduced by 50% = 400
    // Other 4 days: 4 x 800 = 3200
    expect(result.totalProjectCostCents).toBe(3600);
    expect(result.totalChargeableHours).toBe(40);
  });

  it("half-day sick: partial effect", () => {
    const sickRule = makeRule({
      triggerType: "SICK",
      costEffect: "ZERO",
      chargeabilityEffect: "COUNT",
    });

    const result = calculateAllocation({
      lcrCents: 100,
      hoursPerDay: 8,
      startDate: new Date("2026-03-04"), // single Wednesday
      endDate: new Date("2026-03-04"),
      availability: STD_AVAILABILITY,
      absenceDays: [{ date: new Date("2026-03-04"), type: "SICK", isHalfDay: true }],
      calculationRules: [sickRule],
    });

    // Half day sick: 4h worked, 4h absent
    expect(result.totalHours).toBe(4); // worked portion
    expect(result.totalProjectCostCents).toBe(400); // only worked portion charged
    expect(result.totalChargeableHours).toBe(8); // full day counts (COUNT rule)
  });

  it("uses default rules when provided", () => {
    const result = calculateAllocation({
      lcrCents: 100,
      hoursPerDay: 8,
      startDate: new Date("2026-03-02"),
      endDate: new Date("2026-03-06"),
      availability: STD_AVAILABILITY,
      absenceDays: [
        { date: new Date("2026-03-03"), type: "SICK" },
        { date: new Date("2026-03-05"), type: "VACATION" },
      ],
      calculationRules: DEFAULT_CALCULATION_RULES,
    });

    // 3 normal days + 2 absent days (counted as working)
    expect(result.workingDays).toBe(5);
    expect(result.totalHours).toBe(24); // only 3 days actually worked
    expect(result.totalProjectCostCents).toBe(2400); // only 3 days charged
    expect(result.totalChargeableHours).toBe(40); // all 5 days chargeable
  });
});
