import { UpdateVacationStatusSchema } from "@capakraken/shared";
import { VacationStatus } from "@capakraken/db";
import {
  approveVacation,
  batchApproveVacations,
  batchRejectVacations,
  cancelVacation,
  rejectVacation,
} from "@capakraken/application";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { RESOURCE_BRIEF_SELECT } from "../db/selects.js";
import { makeAuditLogger } from "../lib/audit-helpers.js";
import { checkBatchVacationConflicts, checkVacationConflicts } from "../lib/vacation-conflicts.js";
import { emitVacationUpdated } from "../sse/event-bus.js";
import { adminProcedure, managerProcedure, protectedProcedure } from "../trpc.js";
import {
  assertVacationStillChargeable,
  buildVacationApprovalWriteData,
} from "./vacation-chargeability.js";
import { batchCreatePublicHolidayVacations } from "./vacation-public-holidays.js";
import {
  completeVacationApprovalTasks,
  dispatchVacationWebhookInBackground,
  notifyVacationStatusInBackground,
} from "./vacation-side-effects.js";
import {
  assertVacationApprovable,
  assertVacationCancelable,
  assertVacationRejectable,
  buildApprovedVacationUpdateData,
  buildRejectedVacationUpdateData,
  buildVacationStatusUpdateData,
  canActorCancelVacation,
  isVacationManagerRole,
} from "./vacation-management-support.js";

const BatchCreatePublicHolidaysSchema = z.object({
  year: z.number().int().min(2000).max(2100),
  federalState: z.string().optional(),
  chapter: z.string().optional(),
  replaceExisting: z.boolean().default(false),
});

export const vacationManagementProcedures = {
  approve: managerProcedure.input(z.object({ id: z.string() })).mutation(async ({ ctx, input }) => {
    const userRecord = ctx.dbUser;
    const audit = makeAuditLogger(ctx.db, userRecord?.id);

    const result = await approveVacation(
      ctx.db,
      { id: input.id, actorUserId: userRecord?.id },
      {
        assertVacationApprovable,
        assertVacationStillChargeable,
        buildVacationApprovalWriteData,
        checkVacationConflicts,
        buildApprovedVacationUpdateData,
      },
    );

    const { vacation: updated, existingStatus, warnings } = result;

    emitVacationUpdated({ id: updated.id, resourceId: updated.resourceId, status: updated.status });

    audit({
      entityType: "Vacation",
      entityId: updated.id,
      entityName: `Vacation ${updated.id}`,
      action: "UPDATE",
      after: updated as unknown as Record<string, unknown>,
      summary: `Approved vacation (was ${existingStatus})`,
    });

    dispatchVacationWebhookInBackground(ctx.db, "vacation.approved", {
      id: updated.id,
      resourceId: updated.resourceId,
      startDate: updated.startDate.toISOString(),
      endDate: updated.endDate.toISOString(),
    });

    await completeVacationApprovalTasks(ctx.db, input.id, userRecord?.id);

    if (existingStatus === VacationStatus.PENDING) {
      notifyVacationStatusInBackground(
        ctx.db,
        updated.id,
        updated.resourceId,
        VacationStatus.APPROVED,
      );
    }

    return { ...updated, warnings };
  }),

  reject: managerProcedure
    .input(z.object({ id: z.string(), rejectionReason: z.string().max(500).optional() }))
    .mutation(async ({ ctx, input }) => {
      const result = await rejectVacation(
        ctx.db,
        { id: input.id, rejectionReason: input.rejectionReason },
        { assertVacationRejectable, buildRejectedVacationUpdateData },
      );

      const { vacation: updated } = result;

      emitVacationUpdated({
        id: updated.id,
        resourceId: updated.resourceId,
        status: updated.status,
      });

      const userRecord = ctx.dbUser;
      const audit = makeAuditLogger(ctx.db, userRecord?.id);
      await completeVacationApprovalTasks(ctx.db, input.id, userRecord?.id);

      audit({
        entityType: "Vacation",
        entityId: updated.id,
        entityName: `Vacation ${updated.id}`,
        action: "UPDATE",
        after: updated as unknown as Record<string, unknown>,
        summary: `Rejected vacation${input.rejectionReason ? `: ${input.rejectionReason}` : ""}`,
      });

      notifyVacationStatusInBackground(
        ctx.db,
        updated.id,
        updated.resourceId,
        VacationStatus.REJECTED,
        input.rejectionReason,
      );

      return updated;
    }),

  batchApprove: managerProcedure
    .input(z.object({ ids: z.array(z.string()).min(1).max(100) }))
    .mutation(async ({ ctx, input }) => {
      const userRecord = ctx.dbUser;
      const audit = makeAuditLogger(ctx.db, userRecord?.id);

      const result = await batchApproveVacations(
        ctx.db,
        { ids: input.ids, actorUserId: userRecord?.id },
        {
          assertVacationStillChargeable,
          buildVacationApprovalWriteData,
          checkBatchVacationConflicts,
          buildApprovedVacationUpdateData,
        },
      );

      for (const updated of result.updatedVacations) {
        emitVacationUpdated({
          id: updated.id,
          resourceId: updated.resourceId,
          status: updated.status,
        });
        notifyVacationStatusInBackground(
          ctx.db,
          updated.id,
          updated.resourceId,
          VacationStatus.APPROVED,
        );
        await completeVacationApprovalTasks(ctx.db, updated.id, userRecord?.id);
        audit({
          entityType: "Vacation",
          entityId: updated.id,
          entityName: `Vacation ${updated.id}`,
          action: "UPDATE",
          after: updated.existingVacation as unknown as Record<string, unknown>,
          summary: "Batch approved vacation",
        });
      }

      return { approved: result.approved, warnings: result.warnings };
    }),

  batchReject: managerProcedure
    .input(
      z.object({
        ids: z.array(z.string()).min(1).max(100),
        rejectionReason: z.string().max(500).optional(),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      const userRecord = ctx.dbUser;
      const audit = makeAuditLogger(ctx.db, userRecord?.id);

      const result = await batchRejectVacations(
        ctx.db,
        { ids: input.ids, rejectionReason: input.rejectionReason },
        { buildRejectedVacationUpdateData },
      );

      for (const vacation of result.vacations) {
        emitVacationUpdated({
          id: vacation.id,
          resourceId: vacation.resourceId,
          status: VacationStatus.REJECTED,
        });
        notifyVacationStatusInBackground(
          ctx.db,
          vacation.id,
          vacation.resourceId,
          VacationStatus.REJECTED,
          input.rejectionReason,
        );

        audit({
          entityType: "Vacation",
          entityId: vacation.id,
          entityName: `Vacation ${vacation.id}`,
          action: "UPDATE",
          after: {
            status: VacationStatus.REJECTED,
            rejectionReason: input.rejectionReason,
          } as unknown as Record<string, unknown>,
          summary: `Batch rejected vacation${input.rejectionReason ? `: ${input.rejectionReason}` : ""}`,
        });

        await completeVacationApprovalTasks(ctx.db, vacation.id, userRecord?.id);
      }

      return { rejected: result.rejected };
    }),

  cancel: protectedProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const userRecord = ctx.dbUser;
      const audit = makeAuditLogger(ctx.db, userRecord?.id);
      if (!userRecord) {
        throw new TRPCError({ code: "UNAUTHORIZED" });
      }

      const result = await cancelVacation(
        ctx.db,
        {
          id: input.id,
          actorId: userRecord.id,
          actorRole: userRecord.systemRole,
        },
        {
          assertVacationCancelable,
          isVacationManagerRole,
          canActorCancelVacation,
        },
      );

      const { vacation: updated, existingStatus } = result;

      emitVacationUpdated({
        id: updated.id,
        resourceId: updated.resourceId,
        status: updated.status,
      });

      audit({
        entityType: "Vacation",
        entityId: updated.id,
        entityName: `Vacation ${updated.id}`,
        action: "UPDATE",
        after: updated as unknown as Record<string, unknown>,
        summary: `Cancelled vacation (was ${existingStatus})`,
      });

      return updated;
    }),

  getPendingApprovals: managerProcedure.query(async ({ ctx }) => {
    return ctx.db.vacation.findMany({
      where: { status: VacationStatus.PENDING },
      include: {
        resource: { select: { ...RESOURCE_BRIEF_SELECT, chapter: true } },
        requestedBy: { select: { id: true, name: true, email: true } },
      },
      orderBy: { startDate: "asc" },
    });
  }),

  batchCreatePublicHolidays: adminProcedure
    .input(BatchCreatePublicHolidaysSchema)
    .mutation(async ({ ctx, input }) => {
      const adminUser = ctx.dbUser;
      if (!adminUser) {
        throw new TRPCError({ code: "UNAUTHORIZED" });
      }
      const audit = makeAuditLogger(ctx.db, adminUser.id);

      const { created, holidays, resources } = await batchCreatePublicHolidayVacations(
        ctx.db,
        input,
        adminUser.id,
      );

      audit({
        entityType: "Vacation",
        entityId: `public-holidays-${input.year}`,
        entityName: `Public Holidays ${input.year}${input.federalState ? ` (${input.federalState})` : ""}`,
        action: "CREATE",
        after: {
          created,
          holidays,
          resources,
          year: input.year,
          federalState: input.federalState,
        } as unknown as Record<string, unknown>,
        summary: `Batch created ${created} public holidays for ${resources} resources (${input.year})`,
      });

      return { created, holidays, resources };
    }),

  updateStatus: protectedProcedure
    .input(UpdateVacationStatusSchema)
    .mutation(async ({ ctx, input }) => {
      const existing = await ctx.db.vacation.findUnique({ where: { id: input.id } });
      if (!existing) {
        throw new TRPCError({ code: "NOT_FOUND", message: "Vacation not found" });
      }

      const userRecord = ctx.dbUser;
      const audit = makeAuditLogger(ctx.db, userRecord?.id);
      if (!userRecord) {
        throw new TRPCError({ code: "UNAUTHORIZED" });
      }

      if (input.status !== "CANCELLED" && !isVacationManagerRole(userRecord.systemRole)) {
        throw new TRPCError({
          code: "FORBIDDEN",
          message: "Manager role required to approve/reject",
        });
      }

      const updated = await ctx.db.vacation.update({
        where: { id: input.id },
        data: buildVacationStatusUpdateData({
          status: input.status,
          note: input.note,
          approvedById: userRecord.id,
          approvedAt: new Date(),
        }),
      });

      emitVacationUpdated({
        id: updated.id,
        resourceId: updated.resourceId,
        status: updated.status,
      });

      audit({
        entityType: "Vacation",
        entityId: updated.id,
        entityName: `Vacation ${updated.id}`,
        action: "UPDATE",
        before: existing as unknown as Record<string, unknown>,
        after: updated as unknown as Record<string, unknown>,
        summary: `Updated vacation status to ${input.status}`,
      });

      return updated;
    }),
};
