import { VacationStatus, VacationType } from "@capakraken/db";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { RESOURCE_BRIEF_SELECT } from "../db/selects.js";
import { emitVacationCreated } from "../sse/event-bus.js";
import { type TRPCContext } from "../trpc.js";
import { getAnonymizationDirectory } from "../lib/anonymization.js";
import { createAuditEntry } from "../lib/audit.js";
import { VACATION_BALANCE_TYPES } from "../lib/vacation-deduction-snapshot.js";
import { resolveVacationCreationChargeability } from "./vacation-chargeability.js";
import { anonymizeVacationRecord, isSameUtcDay } from "./vacation-read.js";
import { createVacationApprovalTasks } from "./vacation-side-effects.js";

export const CreateVacationRequestSchema = z.object({
  resourceId: z.string(),
  type: z.nativeEnum(VacationType),
  startDate: z.coerce.date(),
  endDate: z.coerce.date(),
  note: z.string().max(500).optional(),
  isHalfDay: z.boolean().optional(),
  halfDayPart: z.enum(["MORNING", "AFTERNOON"]).optional(),
}).superRefine((data, ctx) => {
  if (data.endDate < data.startDate) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: "End date must be after start date",
      path: ["endDate"],
    });
  }

  if (data.isHalfDay && !isSameUtcDay(data.startDate, data.endDate)) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: "Half-day requests must start and end on the same day",
      path: ["isHalfDay"],
    });
  }

  if (data.isHalfDay && !data.halfDayPart) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: "Half-day requests require a half-day part",
      path: ["halfDayPart"],
    });
  }

  if (!data.isHalfDay && data.halfDayPart) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: "Half-day part is only allowed for half-day requests",
      path: ["halfDayPart"],
    });
  }
});

export type CreateVacationRequestInput = z.infer<typeof CreateVacationRequestSchema>;

type VacationCreateContext = Pick<TRPCContext, "db" | "session">;

export async function createVacationRequest(
  ctx: VacationCreateContext,
  input: CreateVacationRequestInput,
) {
  if (input.type === VacationType.PUBLIC_HOLIDAY) {
    throw new TRPCError({
      code: "BAD_REQUEST",
      message: "Public holidays must be managed via Holiday Calendars or the legacy holiday import, not via manual vacation requests",
    });
  }

  const userRecord = await ctx.db.user.findUnique({
    where: { email: ctx.session?.user?.email ?? "" },
    select: { id: true, systemRole: true },
  });
  if (!userRecord) {
    throw new TRPCError({ code: "UNAUTHORIZED" });
  }

  const isManager = userRecord.systemRole === "ADMIN" || userRecord.systemRole === "MANAGER";
  if (!isManager) {
    const resource = await ctx.db.resource.findUnique({
      where: { id: input.resourceId },
      select: { userId: true },
    });
    if (!resource || resource.userId !== userRecord.id) {
      throw new TRPCError({
        code: "FORBIDDEN",
        message: "You can only create vacation requests for your own resource",
      });
    }
  }

  const overlapping = await ctx.db.vacation.findFirst({
    where: {
      resourceId: input.resourceId,
      status: { in: ["APPROVED", "PENDING"] },
      startDate: { lte: input.endDate },
      endDate: { gte: input.startDate },
      ...(VACATION_BALANCE_TYPES.has(input.type)
        ? { type: { not: VacationType.PUBLIC_HOLIDAY } }
        : {}),
    },
  });
  if (overlapping) {
    throw new TRPCError({
      code: "BAD_REQUEST",
      message: "Overlapping vacation already exists for this resource in the selected period",
    });
  }

  const { effectiveDays, deductionSnapshotWriteData } = await resolveVacationCreationChargeability(ctx.db, {
    resourceId: input.resourceId,
    type: input.type,
    startDate: input.startDate,
    endDate: input.endDate,
    isHalfDay: input.isHalfDay ?? false,
  });

  const status = isManager ? VacationStatus.APPROVED : VacationStatus.PENDING;
  const vacation = await ctx.db.vacation.create({
    data: {
      resourceId: input.resourceId,
      type: input.type,
      status,
      startDate: input.startDate,
      endDate: input.endDate,
      ...(input.note !== undefined ? { note: input.note } : {}),
      isHalfDay: input.isHalfDay ?? false,
      ...(input.halfDayPart !== undefined ? { halfDayPart: input.halfDayPart } : {}),
      ...(deductionSnapshotWriteData ?? { deductedDays: 0 }),
      requestedById: userRecord.id,
      ...(isManager ? { approvedById: userRecord.id, approvedAt: new Date() } : {}),
    },
    include: {
      resource: { select: RESOURCE_BRIEF_SELECT },
      requestedBy: { select: { id: true, name: true, email: true } },
    },
  });

  emitVacationCreated({ id: vacation.id, resourceId: vacation.resourceId, status: vacation.status });

  void createAuditEntry({
    db: ctx.db,
    entityType: "Vacation",
    entityId: vacation.id,
    entityName: `${vacation.resource?.displayName ?? "Unknown"} - ${vacation.type}`,
    action: "CREATE",
    userId: userRecord.id,
    after: vacation as unknown as Record<string, unknown>,
    source: "ui",
  });

  if (status === VacationStatus.PENDING) {
    await createVacationApprovalTasks({
      db: ctx.db,
      submittedByUserId: userRecord.id,
      vacationId: vacation.id,
      resourceName: vacation.resource?.displayName ?? "Unknown",
      vacationType: input.type,
      startDate: input.startDate,
      endDate: input.endDate,
    });
  }

  const directory = await getAnonymizationDirectory(ctx.db);
  const result = anonymizeVacationRecord(vacation, directory);
  return effectiveDays === null ? result : { ...result, effectiveDays };
}
