import { describe, expect, it } from "vitest";
import { findMatchingRule, applyCostEffect } from "../rules/engine.js";
import { DEFAULT_CALCULATION_RULES } from "../rules/default-rules.js";
import type { CalculationRule } from "@capakraken/shared";

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("findMatchingRule", () => {
  it("matches by triggerType", () => {
    const rules = [makeRule({ triggerType: "SICK" })];
    const match = findMatchingRule(rules, "SICK");
    expect(match).not.toBeNull();
    expect(match!.costEffect).toBe("ZERO");
  });

  it("returns null when no rules match", () => {
    const rules = [makeRule({ triggerType: "SICK" })];
    const match = findMatchingRule(rules, "VACATION");
    expect(match).toBeNull();
  });

  it("skips inactive rules", () => {
    const rules = [makeRule({ triggerType: "SICK", isActive: false })];
    const match = findMatchingRule(rules, "SICK");
    expect(match).toBeNull();
  });

  it("prefers more specific rules (projectId match)", () => {
    const global = makeRule({ id: "global", triggerType: "SICK", projectId: null });
    const specific = makeRule({ id: "specific", triggerType: "SICK", projectId: "proj_1", costEffect: "CHARGE" });
    const match = findMatchingRule([global, specific], "SICK", "proj_1");
    expect(match!.rule.id).toBe("specific");
    expect(match!.costEffect).toBe("CHARGE");
  });

  it("does not match project-specific rule to wrong project", () => {
    const specific = makeRule({ triggerType: "SICK", projectId: "proj_1" });
    const match = findMatchingRule([specific], "SICK", "proj_2");
    expect(match).toBeNull();
  });

  it("prefers higher specificity over higher priority", () => {
    const highPriority = makeRule({ id: "hp", triggerType: "SICK", priority: 100 });
    const specific = makeRule({ id: "sp", triggerType: "SICK", projectId: "proj_1", priority: 0 });
    const match = findMatchingRule([highPriority, specific], "SICK", "proj_1");
    expect(match!.rule.id).toBe("sp");
  });

  it("breaks specificity ties with priority", () => {
    const lowP = makeRule({ id: "low", triggerType: "SICK", priority: 5 });
    const highP = makeRule({ id: "high", triggerType: "SICK", priority: 10 });
    const match = findMatchingRule([lowP, highP], "SICK");
    expect(match!.rule.id).toBe("high");
  });

  it("matches orderType filter", () => {
    const rule = makeRule({ triggerType: "VACATION", orderType: "CHARGEABLE" as never, costEffect: "REDUCE", costReductionPercent: 50 });
    const match = findMatchingRule([rule], "VACATION", null, "CHARGEABLE");
    expect(match).not.toBeNull();
    expect(match!.costEffect).toBe("REDUCE");
  });

  it("does not match wrong orderType", () => {
    const rule = makeRule({ triggerType: "VACATION", orderType: "CHARGEABLE" as never });
    const match = findMatchingRule([rule], "VACATION", null, "INTERNAL");
    expect(match).toBeNull();
  });

  it("specificity: projectId + orderType > projectId only", () => {
    const projOnly = makeRule({ id: "proj", triggerType: "SICK", projectId: "p1" });
    const both = makeRule({ id: "both", triggerType: "SICK", projectId: "p1", orderType: "CHARGEABLE" as never, costEffect: "REDUCE" });
    const match = findMatchingRule([projOnly, both], "SICK", "p1", "CHARGEABLE");
    expect(match!.rule.id).toBe("both");
  });
});

describe("applyCostEffect", () => {
  it("CHARGE returns full cost", () => {
    expect(applyCostEffect(1000, "CHARGE", null)).toBe(1000);
  });

  it("ZERO returns 0", () => {
    expect(applyCostEffect(1000, "ZERO", null)).toBe(0);
  });

  it("REDUCE applies percentage", () => {
    expect(applyCostEffect(1000, "REDUCE", 30)).toBe(700);
  });

  it("REDUCE with 100% returns 0", () => {
    expect(applyCostEffect(1000, "REDUCE", 100)).toBe(0);
  });

  it("REDUCE with 0% returns full cost", () => {
    expect(applyCostEffect(1000, "REDUCE", 0)).toBe(1000);
  });

  it("REDUCE with null percent returns full cost", () => {
    expect(applyCostEffect(1000, "REDUCE", null)).toBe(1000);
  });
});

describe("DEFAULT_CALCULATION_RULES", () => {
  it("provides vacation, sick, and public holiday rules", () => {
    expect(DEFAULT_CALCULATION_RULES).toHaveLength(3);
    const triggers = DEFAULT_CALCULATION_RULES.map((r) => r.triggerType);
    expect(triggers).toContain("VACATION");
    expect(triggers).toContain("SICK");
    expect(triggers).toContain("PUBLIC_HOLIDAY");
  });

  it("vacation: zero cost, count chargeability", () => {
    const rule = DEFAULT_CALCULATION_RULES.find((r) => r.triggerType === "VACATION")!;
    expect(rule.costEffect).toBe("ZERO");
    expect(rule.chargeabilityEffect).toBe("COUNT");
  });

  it("sick: zero cost, count chargeability", () => {
    const rule = DEFAULT_CALCULATION_RULES.find((r) => r.triggerType === "SICK")!;
    expect(rule.costEffect).toBe("ZERO");
    expect(rule.chargeabilityEffect).toBe("COUNT");
  });

  it("public holiday: zero cost, skip chargeability", () => {
    const rule = DEFAULT_CALCULATION_RULES.find((r) => r.triggerType === "PUBLIC_HOLIDAY")!;
    expect(rule.costEffect).toBe("ZERO");
    expect(rule.chargeabilityEffect).toBe("SKIP");
  });
});
