import { VacationStatus, VacationType } from "@capakraken/db";
import { TRPCError } from "@trpc/server";
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
  createVacationRequest,
  CreateVacationRequestSchema,
} from "../router/vacation-create-support.js";

const {
  emitVacationCreated,
  createAuditEntry,
  createVacationApprovalTasks,
  getAnonymizationDirectory,
  resolveVacationCreationChargeability,
} = vi.hoisted(() => ({
  emitVacationCreated: vi.fn(),
  createAuditEntry: vi.fn(),
  createVacationApprovalTasks: vi.fn(),
  getAnonymizationDirectory: vi.fn(),
  resolveVacationCreationChargeability: vi.fn(),
}));

vi.mock("../sse/event-bus.js", () => ({
  emitVacationCreated,
}));

vi.mock("../lib/audit.js", () => ({
  createAuditEntry,
}));

vi.mock("../lib/anonymization.js", async (importOriginal) => {
  const actual = await importOriginal<typeof import("../lib/anonymization.js")>();
  return {
    ...actual,
    anonymizeResource: vi.fn((value) => value),
    anonymizeUser: vi.fn((value) => value),
    getAnonymizationDirectory,
  };
});

vi.mock("../router/vacation-chargeability.js", () => ({
  resolveVacationCreationChargeability,
}));

vi.mock("../router/vacation-side-effects.js", () => ({
  createVacationApprovalTasks,
}));

function createContext(overrides: Record<string, unknown> = {}) {
  return {
    session: {
      user: { email: "user@example.com", name: "User", image: null },
      expires: "2099-01-01T00:00:00.000Z",
    },
    db: {
      user: {
        findUnique: vi.fn().mockResolvedValue({ id: "user_1", systemRole: "USER" }),
      },
      resource: {
        findUnique: vi.fn().mockResolvedValue({ userId: "user_1" }),
      },
      vacation: {
        findFirst: vi.fn().mockResolvedValue(null),
        create: vi.fn().mockResolvedValue({
          id: "vac_1",
          resourceId: "res_1",
          type: VacationType.ANNUAL,
          status: VacationStatus.PENDING,
          startDate: new Date("2026-06-01T00:00:00.000Z"),
          endDate: new Date("2026-06-03T00:00:00.000Z"),
          isHalfDay: false,
          halfDayPart: null,
          resource: { id: "res_1", displayName: "Alice", eid: "E-001" },
          requestedBy: { id: "user_1", name: "User", email: "user@example.com" },
        }),
      },
    },
    ...overrides,
  };
}

describe("vacation create support", () => {
  beforeEach(() => {
    emitVacationCreated.mockReset();
    createAuditEntry.mockReset();
    createVacationApprovalTasks.mockReset();
    getAnonymizationDirectory.mockReset();
    resolveVacationCreationChargeability.mockReset();

    getAnonymizationDirectory.mockResolvedValue({
      resourcesById: new Map(),
      usersById: new Map(),
    });
    resolveVacationCreationChargeability.mockResolvedValue({
      effectiveDays: 3,
      deductionSnapshotWriteData: { deductedDays: 3, deductionSnapshot: { source: "calendar" } },
    });
  });

  it("validates half-day requests against cross-day ranges", () => {
    expect(() => CreateVacationRequestSchema.parse({
      resourceId: "res_1",
      type: VacationType.ANNUAL,
      startDate: "2026-06-01",
      endDate: "2026-06-02",
      isHalfDay: true,
      halfDayPart: "MORNING",
    })).toThrowError(/Half-day requests must start and end on the same day/);
  });

  it("creates pending vacations for regular users and schedules approval tasks", async () => {
    const ctx = createContext();

    const result = await createVacationRequest(ctx as never, {
      resourceId: "res_1",
      type: VacationType.ANNUAL,
      startDate: new Date("2026-06-01T00:00:00.000Z"),
      endDate: new Date("2026-06-03T00:00:00.000Z"),
      note: "Summer",
      isHalfDay: false,
    });

    expect(ctx.db.vacation.create).toHaveBeenCalledWith(expect.objectContaining({
      data: expect.objectContaining({
        status: VacationStatus.PENDING,
        requestedById: "user_1",
        deductedDays: 3,
      }),
    }));
    expect(createVacationApprovalTasks).toHaveBeenCalledWith(expect.objectContaining({
      submittedByUserId: "user_1",
      vacationId: "vac_1",
    }));
    expect(emitVacationCreated).toHaveBeenCalledWith({
      id: "vac_1",
      resourceId: "res_1",
      status: VacationStatus.PENDING,
    });
    expect(result).toMatchObject({
      id: "vac_1",
      effectiveDays: 3,
    });
  });

  it("auto-approves manager-created vacations without approval tasks", async () => {
    const ctx = createContext({
      db: {
        user: {
          findUnique: vi.fn().mockResolvedValue({ id: "mgr_1", systemRole: "MANAGER" }),
        },
        resource: {
          findUnique: vi.fn(),
        },
        vacation: {
          findFirst: vi.fn().mockResolvedValue(null),
          create: vi.fn().mockResolvedValue({
            id: "vac_mgr",
            resourceId: "res_2",
            type: VacationType.ANNUAL,
            status: VacationStatus.APPROVED,
            startDate: new Date("2026-07-01T00:00:00.000Z"),
            endDate: new Date("2026-07-02T00:00:00.000Z"),
            isHalfDay: false,
            halfDayPart: null,
            resource: { id: "res_2", displayName: "Bob", eid: "E-002" },
            requestedBy: { id: "mgr_1", name: "Manager", email: "user@example.com" },
          }),
        },
      },
    });

    await createVacationRequest(ctx as never, {
      resourceId: "res_2",
      type: VacationType.ANNUAL,
      startDate: new Date("2026-07-01T00:00:00.000Z"),
      endDate: new Date("2026-07-02T00:00:00.000Z"),
      isHalfDay: false,
    });

    expect(ctx.db.resource.findUnique).not.toHaveBeenCalled();
    expect(ctx.db.vacation.create).toHaveBeenCalledWith(expect.objectContaining({
      data: expect.objectContaining({
        status: VacationStatus.APPROVED,
        approvedById: "mgr_1",
      }),
    }));
    expect(createVacationApprovalTasks).not.toHaveBeenCalled();
  });

  it("rejects manual public holiday creation before hitting the database", async () => {
    const ctx = createContext();

    await expect(createVacationRequest(ctx as never, {
      resourceId: "res_1",
      type: VacationType.PUBLIC_HOLIDAY,
      startDate: new Date("2026-12-25T00:00:00.000Z"),
      endDate: new Date("2026-12-25T00:00:00.000Z"),
      isHalfDay: false,
    })).rejects.toThrowError(new TRPCError({
      code: "BAD_REQUEST",
      message: "Public holidays must be managed via Holiday Calendars or the legacy holiday import, not via manual vacation requests",
    }));

    expect(ctx.db.user.findUnique).not.toHaveBeenCalled();
    expect(ctx.db.vacation.create).not.toHaveBeenCalled();
  });
});
