import { describe, expect, it } from "vitest";
import { CreateVacationSchema, UpdateVacationStatusSchema } from "../schemas/vacation.schema.js";
import {
  CreateProjectSchema,
  UpdateProjectSchema,
  StaffingRequirementSchema,
} from "../schemas/project.schema.js";
import {
  CreateCountrySchema,
  UpdateCountrySchema,
  CreateMetroCitySchema,
  UpdateMetroCitySchema,
  SpainScheduleRuleSchema,
} from "../schemas/country.schema.js";
import { VacationType, OrderType, AllocationType, ProjectStatus } from "../types/enums.js";

// ─── VacationSchema ─────────────────────────────────────────────────────────

describe("CreateVacationSchema", () => {
  const validMinimal = {
    resourceId: "res-1",
    type: VacationType.ANNUAL,
    startDate: "2026-06-01",
    endDate: "2026-06-07",
  };

  it("accepts minimal valid input and coerces dates", () => {
    const result = CreateVacationSchema.parse(validMinimal);
    expect(result.resourceId).toBe("res-1");
    expect(result.type).toBe(VacationType.ANNUAL);
    expect(result.startDate).toBeInstanceOf(Date);
    expect(result.endDate).toBeInstanceOf(Date);
    expect(result.note).toBeUndefined();
  });

  it("accepts all VacationType enum values", () => {
    for (const type of Object.values(VacationType)) {
      const result = CreateVacationSchema.parse({ ...validMinimal, type });
      expect(result.type).toBe(type);
    }
  });

  it("accepts full valid input with optional note", () => {
    const result = CreateVacationSchema.parse({
      ...validMinimal,
      type: VacationType.SICK,
      note: "Medical certificate attached",
    });
    expect(result.type).toBe(VacationType.SICK);
    expect(result.note).toBe("Medical certificate attached");
  });

  it("accepts same-day vacation (end === start is valid)", () => {
    const result = CreateVacationSchema.parse({
      ...validMinimal,
      startDate: "2026-08-15",
      endDate: "2026-08-15",
    });
    expect(result.startDate).toEqual(result.endDate);
  });

  it("rejects end date before start date", () => {
    expect(() =>
      CreateVacationSchema.parse({
        ...validMinimal,
        startDate: "2026-06-07",
        endDate: "2026-06-01",
      }),
    ).toThrow("End date must be after start date");
  });

  it("rejects invalid VacationType value", () => {
    expect(() => CreateVacationSchema.parse({ ...validMinimal, type: "WEEKEND" })).toThrow();
  });

  it("rejects note exceeding 500 characters", () => {
    expect(() =>
      CreateVacationSchema.parse({
        ...validMinimal,
        note: "x".repeat(501),
      }),
    ).toThrow();
  });

  it("accepts note at exactly 500 characters", () => {
    const result = CreateVacationSchema.parse({
      ...validMinimal,
      note: "x".repeat(500),
    });
    expect(result.note).toHaveLength(500);
  });

  it("rejects missing resourceId", () => {
    const { resourceId: _, ...withoutResourceId } = validMinimal;
    expect(() => CreateVacationSchema.parse(withoutResourceId)).toThrow();
  });
});

describe("UpdateVacationStatusSchema", () => {
  it("accepts valid APPROVED status", () => {
    const result = UpdateVacationStatusSchema.parse({
      id: "vac-1",
      status: "APPROVED",
    });
    expect(result.id).toBe("vac-1");
    expect(result.status).toBe("APPROVED");
    expect(result.note).toBeUndefined();
  });

  it("accepts all valid status values", () => {
    for (const status of ["APPROVED", "REJECTED", "CANCELLED"] as const) {
      const result = UpdateVacationStatusSchema.parse({ id: "vac-1", status });
      expect(result.status).toBe(status);
    }
  });

  it("accepts optional note with status", () => {
    const result = UpdateVacationStatusSchema.parse({
      id: "vac-42",
      status: "REJECTED",
      note: "Insufficient notice period",
    });
    expect(result.note).toBe("Insufficient notice period");
  });

  it("rejects invalid status value", () => {
    expect(() => UpdateVacationStatusSchema.parse({ id: "vac-1", status: "PENDING" })).toThrow();
  });

  it("rejects note exceeding 500 characters", () => {
    expect(() =>
      UpdateVacationStatusSchema.parse({
        id: "vac-1",
        status: "CANCELLED",
        note: "y".repeat(501),
      }),
    ).toThrow();
  });

  it("rejects missing id", () => {
    expect(() => UpdateVacationStatusSchema.parse({ status: "APPROVED" })).toThrow();
  });
});

// ─── ProjectSchema ──────────────────────────────────────────────────────────

describe("StaffingRequirementSchema", () => {
  const validReq = {
    role: "3D Artist",
    requiredSkills: ["Houdini", "Nuke"],
    hoursPerDay: 8,
    headcount: 2,
  };

  it("accepts minimal valid input and generates a uuid id", () => {
    const result = StaffingRequirementSchema.parse(validReq);
    expect(result.role).toBe("3D Artist");
    expect(result.requiredSkills).toEqual(["Houdini", "Nuke"]);
    expect(result.hoursPerDay).toBe(8);
    expect(result.headcount).toBe(2);
    expect(result.id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
  });

  it("accepts full input with all optional fields", () => {
    const result = StaffingRequirementSchema.parse({
      ...validReq,
      id: "11111111-1111-1111-1111-111111111111",
      preferredSkills: ["Maya"],
      startDate: "2026-05-01",
      endDate: "2026-08-31",
      notes: "Experienced in film production",
      chapter: "3D",
    });
    expect(result.id).toBe("11111111-1111-1111-1111-111111111111");
    expect(result.preferredSkills).toEqual(["Maya"]);
    expect(result.chapter).toBe("3D");
  });

  it("rejects empty role string", () => {
    expect(() => StaffingRequirementSchema.parse({ ...validReq, role: "" })).toThrow();
  });

  it("rejects hoursPerDay above 24", () => {
    expect(() => StaffingRequirementSchema.parse({ ...validReq, hoursPerDay: 24.1 })).toThrow();
  });

  it("rejects headcount below 1", () => {
    expect(() => StaffingRequirementSchema.parse({ ...validReq, headcount: 0 })).toThrow();
  });

  it("rejects non-integer headcount", () => {
    expect(() => StaffingRequirementSchema.parse({ ...validReq, headcount: 1.5 })).toThrow();
  });
});

describe("CreateProjectSchema", () => {
  const validMinimal = {
    shortCode: "PROJ-001",
    name: "Hero Sequence VFX",
    orderType: OrderType.CHARGEABLE,
    allocationType: AllocationType.INT,
    budgetCents: 500_000_00,
    startDate: "2026-05-01",
    endDate: "2026-09-30",
    responsiblePerson: "Jane Doe",
  };

  it("accepts minimal valid input with expected defaults", () => {
    const result = CreateProjectSchema.parse(validMinimal);
    expect(result.shortCode).toBe("PROJ-001");
    expect(result.name).toBe("Hero Sequence VFX");
    expect(result.orderType).toBe(OrderType.CHARGEABLE);
    expect(result.allocationType).toBe(AllocationType.INT);
    expect(result.winProbability).toBe(100);
    expect(result.status).toBe(ProjectStatus.DRAFT);
    expect(result.staffingReqs).toEqual([]);
    expect(result.dynamicFields).toEqual({});
    expect(result.startDate).toBeInstanceOf(Date);
    expect(result.endDate).toBeInstanceOf(Date);
  });

  it("accepts full valid input with all optional fields", () => {
    const result = CreateProjectSchema.parse({
      ...validMinimal,
      winProbability: 75,
      staffingReqs: [
        {
          role: "Compositor",
          requiredSkills: ["Nuke"],
          hoursPerDay: 8,
          headcount: 1,
        },
      ],
      dynamicFields: { clientContact: "John Smith" },
      blueprintId: "bp-1",
      status: ProjectStatus.ACTIVE,
      color: "#3b82f6",
      utilizationCategoryId: "uc-1",
      clientId: "client-1",
      shoringThreshold: 20,
      onshoreCountryCode: "DE",
    });
    expect(result.winProbability).toBe(75);
    expect(result.staffingReqs).toHaveLength(1);
    expect(result.color).toBe("#3b82f6");
    expect(result.shoringThreshold).toBe(20);
    expect(result.onshoreCountryCode).toBe("DE");
  });

  it("rejects end date before start date", () => {
    expect(() =>
      CreateProjectSchema.parse({
        ...validMinimal,
        startDate: "2026-09-30",
        endDate: "2026-05-01",
      }),
    ).toThrow("End date must be after start date");
  });

  it("rejects shortCode with lowercase letters", () => {
    expect(() => CreateProjectSchema.parse({ ...validMinimal, shortCode: "proj-001" })).toThrow();
  });

  it("rejects shortCode with spaces", () => {
    expect(() => CreateProjectSchema.parse({ ...validMinimal, shortCode: "PROJ 001" })).toThrow();
  });

  it("rejects shortCode longer than 20 characters", () => {
    expect(() =>
      CreateProjectSchema.parse({
        ...validMinimal,
        shortCode: "A".repeat(21),
      }),
    ).toThrow();
  });

  it("rejects winProbability above 100", () => {
    expect(() => CreateProjectSchema.parse({ ...validMinimal, winProbability: 101 })).toThrow();
  });

  it("rejects negative budgetCents", () => {
    expect(() => CreateProjectSchema.parse({ ...validMinimal, budgetCents: -1 })).toThrow();
  });

  it("rejects invalid hex color format", () => {
    expect(() => CreateProjectSchema.parse({ ...validMinimal, color: "blue" })).toThrow();
  });

  it("rejects invalid OrderType", () => {
    expect(() =>
      CreateProjectSchema.parse({ ...validMinimal, orderType: "FIXED_PRICE" }),
    ).toThrow();
  });

  it("rejects empty responsiblePerson", () => {
    expect(() => CreateProjectSchema.parse({ ...validMinimal, responsiblePerson: "" })).toThrow();
  });

  it("accepts same-day project (start === end)", () => {
    const result = CreateProjectSchema.parse({
      ...validMinimal,
      startDate: "2026-07-01",
      endDate: "2026-07-01",
    });
    expect(result.startDate).toEqual(result.endDate);
  });
});

describe("UpdateProjectSchema", () => {
  it("accepts empty object (all fields optional)", () => {
    const result = UpdateProjectSchema.parse({});
    expect(result).toEqual({});
  });

  it("accepts partial update with only name", () => {
    const result = UpdateProjectSchema.parse({ name: "Renamed Project" });
    expect(result.name).toBe("Renamed Project");
    expect(result.shortCode).toBeUndefined();
    expect(result.status).toBeUndefined();
  });

  it("accepts partial update changing status", () => {
    const result = UpdateProjectSchema.parse({ status: ProjectStatus.ON_HOLD });
    expect(result.status).toBe(ProjectStatus.ON_HOLD);
  });

  it("rejects invalid shortCode even in partial update", () => {
    expect(() => UpdateProjectSchema.parse({ shortCode: "invalid code!" })).toThrow();
  });

  it("does NOT enforce date-range order (no refine on UpdateProjectSchema)", () => {
    // UpdateProjectSchema is a plain partial — no cross-field refinement
    expect(() =>
      UpdateProjectSchema.parse({
        startDate: "2026-12-31",
        endDate: "2026-01-01",
      }),
    ).not.toThrow();
  });
});

// ─── CountrySchema ──────────────────────────────────────────────────────────

describe("SpainScheduleRuleSchema", () => {
  const validSpainRule = {
    type: "spain" as const,
    fridayHours: 6,
    summerPeriod: { from: "06-15", to: "09-15" },
    summerHours: 7,
    regularHours: 8,
  };

  it("accepts valid Spain schedule rule", () => {
    const result = SpainScheduleRuleSchema.parse(validSpainRule);
    expect(result.type).toBe("spain");
    expect(result.fridayHours).toBe(6);
    expect(result.summerPeriod.from).toBe("06-15");
    expect(result.regularHours).toBe(8);
  });

  it("rejects wrong type literal", () => {
    expect(() => SpainScheduleRuleSchema.parse({ ...validSpainRule, type: "germany" })).toThrow();
  });

  it("rejects invalid summerPeriod date format (wrong separator)", () => {
    expect(() =>
      SpainScheduleRuleSchema.parse({
        ...validSpainRule,
        summerPeriod: { from: "2026-06-15", to: "09-15" },
      }),
    ).toThrow();
  });

  it("rejects non-positive fridayHours", () => {
    expect(() => SpainScheduleRuleSchema.parse({ ...validSpainRule, fridayHours: 0 })).toThrow();
  });

  it("rejects non-positive summerHours", () => {
    expect(() => SpainScheduleRuleSchema.parse({ ...validSpainRule, summerHours: -1 })).toThrow();
  });
});

describe("CreateCountrySchema", () => {
  const validMinimal = {
    code: "de",
    name: "Germany",
  };

  it("accepts minimal input and uppercases code, applies default hours", () => {
    const result = CreateCountrySchema.parse(validMinimal);
    expect(result.code).toBe("DE");
    expect(result.name).toBe("Germany");
    expect(result.dailyWorkingHours).toBe(8);
    expect(result.scheduleRules).toBeUndefined();
  });

  it("accepts 3-letter country code and uppercases it", () => {
    const result = CreateCountrySchema.parse({ code: "deu", name: "Germany" });
    expect(result.code).toBe("DEU");
  });

  it("accepts full input with custom hours and Spain schedule rules", () => {
    const result = CreateCountrySchema.parse({
      code: "ES",
      name: "Spain",
      dailyWorkingHours: 7.5,
      scheduleRules: {
        type: "spain",
        fridayHours: 6,
        summerPeriod: { from: "06-15", to: "09-15" },
        summerHours: 7,
        regularHours: 8,
      },
    });
    expect(result.code).toBe("ES");
    expect(result.dailyWorkingHours).toBe(7.5);
    expect(result.scheduleRules?.type).toBe("spain");
  });

  it("accepts null scheduleRules explicitly", () => {
    const result = CreateCountrySchema.parse({ ...validMinimal, scheduleRules: null });
    expect(result.scheduleRules).toBeNull();
  });

  it("rejects country code shorter than 2 characters", () => {
    expect(() => CreateCountrySchema.parse({ code: "D", name: "Germany" })).toThrow();
  });

  it("rejects country code longer than 3 characters", () => {
    expect(() => CreateCountrySchema.parse({ code: "DEUR", name: "Germany" })).toThrow();
  });

  it("rejects dailyWorkingHours above 24", () => {
    expect(() => CreateCountrySchema.parse({ ...validMinimal, dailyWorkingHours: 25 })).toThrow();
  });

  it("rejects non-positive dailyWorkingHours", () => {
    expect(() => CreateCountrySchema.parse({ ...validMinimal, dailyWorkingHours: 0 })).toThrow();
  });

  it("rejects empty name", () => {
    expect(() => CreateCountrySchema.parse({ code: "DE", name: "" })).toThrow();
  });
});

describe("UpdateCountrySchema", () => {
  it("accepts empty object (all fields optional)", () => {
    const result = UpdateCountrySchema.parse({});
    expect(result).toEqual({});
  });

  it("accepts partial update with isActive flag", () => {
    const result = UpdateCountrySchema.parse({ isActive: false });
    expect(result.isActive).toBe(false);
    expect(result.name).toBeUndefined();
  });

  it("accepts partial update with only name", () => {
    const result = UpdateCountrySchema.parse({ name: "Deutschland" });
    expect(result.name).toBe("Deutschland");
    expect(result.isActive).toBeUndefined();
  });

  it("uppercases code in partial update", () => {
    const result = UpdateCountrySchema.parse({ code: "fr" });
    expect(result.code).toBe("FR");
  });

  it("rejects invalid dailyWorkingHours in partial update", () => {
    expect(() => UpdateCountrySchema.parse({ dailyWorkingHours: 30 })).toThrow();
  });
});

describe("CreateMetroCitySchema", () => {
  it("accepts valid input", () => {
    const result = CreateMetroCitySchema.parse({
      name: "Munich",
      countryId: "country-1",
    });
    expect(result.name).toBe("Munich");
    expect(result.countryId).toBe("country-1");
  });

  it("rejects empty name", () => {
    expect(() => CreateMetroCitySchema.parse({ name: "", countryId: "country-1" })).toThrow();
  });

  it("rejects name longer than 100 characters", () => {
    expect(() =>
      CreateMetroCitySchema.parse({
        name: "A".repeat(101),
        countryId: "country-1",
      }),
    ).toThrow();
  });

  it("accepts name at exactly 100 characters", () => {
    const result = CreateMetroCitySchema.parse({
      name: "A".repeat(100),
      countryId: "country-1",
    });
    expect(result.name).toHaveLength(100);
  });

  it("rejects missing countryId", () => {
    expect(() => CreateMetroCitySchema.parse({ name: "Munich" })).toThrow();
  });
});

describe("UpdateMetroCitySchema", () => {
  it("accepts empty object (name is optional)", () => {
    const result = UpdateMetroCitySchema.parse({});
    expect(result.name).toBeUndefined();
  });

  it("accepts valid name update", () => {
    const result = UpdateMetroCitySchema.parse({ name: "Berlin" });
    expect(result.name).toBe("Berlin");
  });

  it("rejects empty string for name", () => {
    expect(() => UpdateMetroCitySchema.parse({ name: "" })).toThrow();
  });

  it("rejects name longer than 100 characters", () => {
    expect(() => UpdateMetroCitySchema.parse({ name: "B".repeat(101) })).toThrow();
  });
});
