import { describe, expect, it, vi } from "vitest";

vi.mock("../lib/resource-capacity.js", () => ({
  getAvailabilityHoursForDate: vi.fn().mockReturnValue(8),
  calculateEffectiveDayAvailability: vi.fn().mockReturnValue(8),
}));

import {
  getAvailabilityHoursForDate,
  calculateEffectiveDayAvailability,
} from "../lib/resource-capacity.js";
import {
  ACTIVE_STATUSES,
  createDateRange,
  getBaseDayAvailability,
  getEffectiveDayAvailability,
  createLocationLabel,
  calculateAllocatedHoursForDay,
} from "../router/staffing-shared.js";

// ─── ACTIVE_STATUSES ──────────────────────────────────────────────────────────

describe("ACTIVE_STATUSES", () => {
  it("contains PROPOSED, CONFIRMED and ACTIVE", () => {
    expect(ACTIVE_STATUSES.has("PROPOSED")).toBe(true);
    expect(ACTIVE_STATUSES.has("CONFIRMED")).toBe(true);
    expect(ACTIVE_STATUSES.has("ACTIVE")).toBe(true);
  });

  it("does not contain DRAFT, CANCELLED or COMPLETED", () => {
    expect(ACTIVE_STATUSES.has("DRAFT")).toBe(false);
    expect(ACTIVE_STATUSES.has("CANCELLED")).toBe(false);
    expect(ACTIVE_STATUSES.has("COMPLETED")).toBe(false);
  });
});

// ─── createDateRange ──────────────────────────────────────────────────────────

describe("createDateRange", () => {
  it("uses startDate and endDate when both provided", () => {
    const start = new Date("2026-04-10");
    const end = new Date("2026-04-20");
    const result = createDateRange({ startDate: start, endDate: end });
    expect(result.startDate.toISOString()).toBe("2026-04-10T00:00:00.000Z");
    expect(result.endDate.toISOString()).toBe("2026-04-20T00:00:00.000Z");
  });

  it("defaults endDate to startDate + 20 days when only startDate given (21-day duration)", () => {
    const start = new Date("2026-04-10");
    const result = createDateRange({ startDate: start });
    expect(result.startDate.toISOString()).toBe("2026-04-10T00:00:00.000Z");
    expect(result.endDate.toISOString()).toBe("2026-04-30T00:00:00.000Z");
  });

  it("defaults startDate to today when not provided", () => {
    const todayIso = new Date().toISOString().slice(0, 10);
    const result = createDateRange({});
    expect(result.startDate.toISOString().startsWith(todayIso)).toBe(true);
  });

  it("applies custom durationDays so that endDate = startDate + durationDays - 1", () => {
    const start = new Date("2026-04-10");
    const result = createDateRange({ startDate: start, durationDays: 10 });
    expect(result.startDate.toISOString()).toBe("2026-04-10T00:00:00.000Z");
    expect(result.endDate.toISOString()).toBe("2026-04-19T00:00:00.000Z");
  });

  it("throws when endDate is before startDate", () => {
    const start = new Date("2026-04-20");
    const end = new Date("2026-04-10");
    expect(() => createDateRange({ startDate: start, endDate: end })).toThrow(
      "endDate must be on or after startDate.",
    );
  });
});

// ─── getBaseDayAvailability ───────────────────────────────────────────────────

describe("getBaseDayAvailability", () => {
  const availability = {
    monday: 8,
    tuesday: 8,
    wednesday: 8,
    thursday: 8,
    friday: 8,
  };
  const date = new Date("2026-04-10");

  it("calls getAvailabilityHoursForDate with the correct arguments", () => {
    getBaseDayAvailability(availability, date);
    expect(getAvailabilityHoursForDate).toHaveBeenCalledWith(availability, date);
  });

  it("returns the value from the mock", () => {
    const result = getBaseDayAvailability(availability, date);
    expect(result).toBe(8);
  });
});

// ─── getEffectiveDayAvailability ──────────────────────────────────────────────

describe("getEffectiveDayAvailability", () => {
  const availability = {
    monday: 8,
    tuesday: 8,
    wednesday: 8,
    thursday: 8,
    friday: 8,
  };
  const date = new Date("2026-04-10");
  const context = {
    absenceFractionsByDate: new Map<string, number>(),
    holidayDates: new Set<string>(),
    vacationFractionsByDate: new Map<string, number>(),
  };

  it("calls calculateEffectiveDayAvailability with the correct arguments", () => {
    getEffectiveDayAvailability(availability, date, context);
    expect(calculateEffectiveDayAvailability).toHaveBeenCalledWith({
      availability,
      date,
      context,
    });
  });

  it("passes the context parameter through to the underlying function", () => {
    getEffectiveDayAvailability(availability, date, undefined);
    expect(calculateEffectiveDayAvailability).toHaveBeenCalledWith({
      availability,
      date,
      context: undefined,
    });
  });
});

// ─── createLocationLabel ──────────────────────────────────────────────────────

describe("createLocationLabel", () => {
  it("returns all three parts joined with ' / ' when all values are present", () => {
    expect(
      createLocationLabel({ countryCode: "DE", federalState: "Bavaria", metroCityName: "Munich" }),
    ).toBe("DE / Bavaria / Munich");
  });

  it("returns only countryCode when federalState and metroCityName are absent", () => {
    expect(createLocationLabel({ countryCode: "DE" })).toBe("DE");
  });

  it("returns empty string when all parts are null or undefined", () => {
    expect(
      createLocationLabel({ countryCode: null, federalState: null, metroCityName: null }),
    ).toBe("");
    expect(createLocationLabel({})).toBe("");
  });

  it("omits entries that consist entirely of whitespace", () => {
    expect(
      createLocationLabel({ countryCode: "DE", federalState: "   ", metroCityName: "Munich" }),
    ).toBe("DE / Munich");
  });
});

// ─── calculateAllocatedHoursForDay ────────────────────────────────────────────

describe("calculateAllocatedHoursForDay", () => {
  const date = new Date("2026-04-10T00:00:00.000Z");
  const emptyContext = {
    absenceFractionsByDate: new Map<string, number>(),
    holidayDates: new Set<string>(),
    vacationFractionsByDate: new Map<string, number>(),
  };

  it("sums hoursPerDay for all active bookings that overlap the date", () => {
    const bookings = [
      {
        startDate: new Date("2026-04-01"),
        endDate: new Date("2026-04-30"),
        hoursPerDay: 6,
        status: "CONFIRMED",
      },
      {
        startDate: new Date("2026-04-01"),
        endDate: new Date("2026-04-30"),
        hoursPerDay: 2,
        status: "ACTIVE",
      },
    ];
    const result = calculateAllocatedHoursForDay({ bookings, date, context: emptyContext });
    expect(result.allocatedHours).toBe(8);
  });

  it("ignores bookings with non-active statuses such as DRAFT and CANCELLED", () => {
    const bookings = [
      {
        startDate: new Date("2026-04-01"),
        endDate: new Date("2026-04-30"),
        hoursPerDay: 8,
        status: "DRAFT",
      },
      {
        startDate: new Date("2026-04-01"),
        endDate: new Date("2026-04-30"),
        hoursPerDay: 4,
        status: "CANCELLED",
      },
      {
        startDate: new Date("2026-04-01"),
        endDate: new Date("2026-04-30"),
        hoursPerDay: 3,
        status: "CONFIRMED",
      },
    ];
    const result = calculateAllocatedHoursForDay({ bookings, date, context: emptyContext });
    expect(result.allocatedHours).toBe(3);
  });

  it("ignores bookings whose date range does not overlap the target date", () => {
    const bookings = [
      {
        startDate: new Date("2026-03-01"),
        endDate: new Date("2026-04-09"),
        hoursPerDay: 8,
        status: "ACTIVE",
      },
      {
        startDate: new Date("2026-04-11"),
        endDate: new Date("2026-04-30"),
        hoursPerDay: 8,
        status: "ACTIVE",
      },
      {
        startDate: new Date("2026-04-10"),
        endDate: new Date("2026-04-10"),
        hoursPerDay: 4,
        status: "CONFIRMED",
      },
    ];
    const result = calculateAllocatedHoursForDay({ bookings, date, context: emptyContext });
    expect(result.allocatedHours).toBe(4);
  });

  it("separates chargeable hours from non-chargeable hours", () => {
    const bookings = [
      {
        startDate: new Date("2026-04-01"),
        endDate: new Date("2026-04-30"),
        hoursPerDay: 6,
        status: "CONFIRMED",
        isChargeable: true,
      },
      {
        startDate: new Date("2026-04-01"),
        endDate: new Date("2026-04-30"),
        hoursPerDay: 2,
        status: "ACTIVE",
        isChargeable: false,
      },
    ];
    const result = calculateAllocatedHoursForDay({ bookings, date, context: emptyContext });
    expect(result.allocatedHours).toBe(8);
    expect(result.chargeableHours).toBe(6);
  });

  it("applies absenceFraction as dayFraction so that effective hours are scaled accordingly", () => {
    const context = {
      absenceFractionsByDate: new Map([["2026-04-10", 0.5]]),
      holidayDates: new Set<string>(),
      vacationFractionsByDate: new Map<string, number>(),
    };
    const bookings = [
      {
        startDate: new Date("2026-04-01"),
        endDate: new Date("2026-04-30"),
        hoursPerDay: 8,
        status: "CONFIRMED",
        isChargeable: true,
      },
    ];
    const result = calculateAllocatedHoursForDay({ bookings, date, context });
    // dayFraction = 1 - 0.5 = 0.5 → 8 * 0.5 = 4
    expect(result.allocatedHours).toBe(4);
    expect(result.chargeableHours).toBe(4);
  });
});
