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

const { calculateAllocation } = vi.hoisted(() => ({
  calculateAllocation: vi.fn(),
}));

const { calculateEffectiveBookedHours } = vi.hoisted(() => ({
  calculateEffectiveBookedHours: vi.fn(),
}));

vi.mock("@capakraken/engine/allocation", () => ({
  calculateAllocation,
}));

vi.mock("../lib/resource-capacity.js", () => ({
  calculateEffectiveBookedHours,
  calculateEffectiveAvailableHours: vi.fn(),
  loadResourceDailyAvailabilityContexts: vi.fn().mockResolvedValue(new Map()),
}));

import {
  collectScenarioSkillSet,
  DEFAULT_AVAILABILITY,
  getScenarioAvailability,
  roundToTenths,
  calculateScenarioEntryHours,
} from "../router/scenario-shared.js";

describe("roundToTenths", () => {
  it("rounds 0.15 up to 0.2", () => {
    expect(roundToTenths(0.15)).toBe(0.2);
  });

  it("rounds 0.34 down to 0.3", () => {
    expect(roundToTenths(0.34)).toBe(0.3);
  });

  it("leaves an integer unchanged", () => {
    expect(roundToTenths(5)).toBe(5);
  });
});

describe("getScenarioAvailability", () => {
  it("returns DEFAULT_AVAILABILITY when input is null", () => {
    expect(getScenarioAvailability(null)).toEqual(DEFAULT_AVAILABILITY);
  });

  it("returns DEFAULT_AVAILABILITY when input is undefined", () => {
    expect(getScenarioAvailability(undefined)).toEqual(DEFAULT_AVAILABILITY);
  });

  it("returns the value as-is when a valid availability object is passed", () => {
    const custom = {
      monday: 6,
      tuesday: 6,
      wednesday: 6,
      thursday: 6,
      friday: 6,
      saturday: 0,
      sunday: 0,
    };
    expect(getScenarioAvailability(custom)).toBe(custom);
  });
});

describe("collectScenarioSkillSet", () => {
  it("returns an empty Set for null input", () => {
    expect(collectScenarioSkillSet(null)).toEqual(new Set());
  });

  it("returns an empty Set for an empty array", () => {
    expect(collectScenarioSkillSet([])).toEqual(new Set());
  });

  it("normalizes skill names to lowercase", () => {
    const result = collectScenarioSkillSet([{ skill: "Houdini" }]);
    expect(result).toEqual(new Set(["houdini"]));
  });

  it("deduplicates case-insensitive skill variants (e.g. 'Houdini' and 'houdini')", () => {
    const result = collectScenarioSkillSet([
      { skill: "Houdini" },
      { skill: "houdini" },
      { skill: "HOUDINI" },
    ]);
    expect(result.size).toBe(1);
    expect(result).toContain("houdini");
  });

  it("filters out blank-string or whitespace-only skill values", () => {
    const result = collectScenarioSkillSet([
      { skill: "" },
      { skill: "   " },
      { skill: "Maya" },
    ]);
    expect(result).toEqual(new Set(["maya"]));
  });
});

describe("calculateScenarioEntryHours", () => {
  const baseEntry = {
    lcrCents: 500,
    hoursPerDay: 8,
    startDate: new Date("2026-04-01T00:00:00.000Z"),
    endDate: new Date("2026-04-30T00:00:00.000Z"),
    availability: DEFAULT_AVAILABILITY,
  };

  const baseOptions = {
    periodStart: new Date("2026-04-01T00:00:00.000Z"),
    periodEnd: new Date("2026-04-30T00:00:00.000Z"),
    contexts: new Map(),
  };

  it("delegates to calculateAllocation when resourceId is null", () => {
    calculateAllocation.mockReturnValue({ totalHours: 160 });

    const result = calculateScenarioEntryHours(
      { ...baseEntry, resourceId: null },
      baseOptions,
    );

    expect(calculateAllocation).toHaveBeenCalledWith({
      lcrCents: baseEntry.lcrCents,
      hoursPerDay: baseEntry.hoursPerDay,
      startDate: baseEntry.startDate,
      endDate: baseEntry.endDate,
      availability: baseEntry.availability,
    });
    expect(result).toBe(160);
  });

  it("delegates to calculateEffectiveBookedHours when resourceId is set", () => {
    calculateEffectiveBookedHours.mockReturnValue(140);
    const contexts = new Map([["resource_1", { someContext: true }]]);

    const result = calculateScenarioEntryHours(
      { ...baseEntry, resourceId: "resource_1" },
      { ...baseOptions, contexts },
    );

    expect(calculateEffectiveBookedHours).toHaveBeenCalledWith({
      availability: baseEntry.availability,
      startDate: baseEntry.startDate,
      endDate: baseEntry.endDate,
      hoursPerDay: baseEntry.hoursPerDay,
      periodStart: baseOptions.periodStart,
      periodEnd: baseOptions.periodEnd,
      context: { someContext: true },
    });
    expect(result).toBe(140);
  });
});
