import { FastifyInstance } from "fastify";
import {
  attendance_leave_type,
  leave_requests_leave_type,
  leave_requests_status,
} from "@prisma/client";
import prisma from "../../config/database";
import { requireAuth, requirePermission } from "../../middleware/auth";
import { logAudit } from "../../services/audit";
import { success, error, parseId, paginated } from "../../utils/response";
import { parsePagination, buildPaginationMeta } from "../../utils/pagination";
import { parseBody } from "../../schemas/common";
import {
  CreateLeaveRequestSchema,
  ReviewLeaveRequestSchema,
} from "../../schemas/leave-requests.schema";
import { notifyNewLeaveRequest } from "../../services/leave-notification";

const VALID_LEAVE_TYPES = ["vacation", "sick", "unpaid"] as const;
const VALID_REVIEW_STATUSES = ["approved", "rejected"] as const;

export default async function leaveRequestsRoutes(
  fastify: FastifyInstance,
): Promise<void> {
  fastify.get("/", { preHandler: requireAuth }, async (request, reply) => {
    const query = request.query as Record<string, unknown>;
    const { page, limit, skip, order } = parsePagination(query);
    const authData = request.authData!;
    const isAdmin = authData.permissions.includes("attendance.approve");

    const where: Record<string, unknown> = {};
    if (!isAdmin || query.mine === "1") where.user_id = authData.userId;
    else if (query.user_id) {
      const filterUserId = Number(query.user_id);
      if (!Number.isFinite(filterUserId))
        return error(reply, "Neplatné ID uživatele", 400);
      where.user_id = filterUserId;
    }
    if (query.status) {
      const statuses = String(query.status).split(",");
      where.status = statuses.length === 1 ? statuses[0] : { in: statuses };
    }

    const [requests, total] = await Promise.all([
      prisma.leave_requests.findMany({
        where,
        skip,
        take: limit,
        orderBy: { created_at: order },
        include: {
          users_leave_requests_user_idTousers: {
            select: { id: true, first_name: true, last_name: true },
          },
          users_leave_requests_reviewer_idTousers: {
            select: { id: true, first_name: true, last_name: true },
          },
        },
      }),
      prisma.leave_requests.count({ where }),
    ]);

    return paginated(reply, requests, buildPaginationMeta(total, page, limit));
  });

  fastify.post("/", { preHandler: requireAuth }, async (request, reply) => {
    const parsed = parseBody(CreateLeaveRequestSchema, request.body);
    if ("error" in parsed) return error(reply, parsed.error, 400);
    const body = parsed.data;
    const authData = request.authData!;

    const leaveType = body.leave_type;
    if (
      !VALID_LEAVE_TYPES.includes(
        leaveType as (typeof VALID_LEAVE_TYPES)[number],
      )
    ) {
      return error(reply, "Neplatný typ nepřítomnosti", 400);
    }

    const dateFrom = new Date(body.date_from);
    const dateTo = new Date(body.date_to);

    if (isNaN(dateFrom.getTime()) || isNaN(dateTo.getTime())) {
      return error(reply, "Neplatné datum", 400);
    }
    if (dateTo < dateFrom) {
      return error(reply, "Datum do musí být po datu od", 400);
    }

    // Compute business days server-side (matching PHP logic)
    let businessDays = 0;
    const current = new Date(dateFrom);
    while (current <= dateTo) {
      const day = current.getDay();
      if (day !== 0 && day !== 6) businessDays++;
      current.setDate(current.getDate() + 1);
    }

    if (businessDays === 0) {
      return error(reply, "Zvolený rozsah neobsahuje žádné pracovní dny", 400);
    }

    const leaveRequest = await prisma.leave_requests.create({
      data: {
        user_id: authData.userId,
        leave_type: leaveType as leave_requests_leave_type,
        date_from: dateFrom,
        date_to: dateTo,
        total_hours: businessDays * 8,
        total_days: businessDays,
        notes: body.notes ? String(body.notes) : null,
        status: "pending",
      },
    });

    await logAudit({
      request,
      authData,
      action: "create",
      entityType: "leave_request",
      entityId: leaveRequest.id,
      description: `Vytvořena žádost o nepřítomnost`,
    });

    // Send email notification (non-blocking)
    try {
      const employeeName =
        `${authData.firstName} ${authData.lastName}`.trim() ||
        authData.username;
      notifyNewLeaveRequest(
        {
          leave_type: leaveType,
          date_from: body.date_from,
          date_to: body.date_to,
          total_days: businessDays,
          total_hours: businessDays * 8,
          notes: body.notes,
        },
        employeeName,
      ).catch((err) => request.log.error(err, "Leave notification error"));
    } catch (err) {
      request.log.error(err, "Leave notification error");
    }

    return success(
      reply,
      { id: leaveRequest.id },
      201,
      "Žádost byla odeslána ke schválení",
    );
  });

  // PUT /api/admin/leave-requests/:id (approve/reject)
  fastify.put<{ Params: { id: string } }>(
    "/:id",
    { preHandler: requirePermission("attendance.approve") },
    async (request, reply) => {
      const id = parseId(request.params.id, reply);
      if (id === null) return;
      const parsed = parseBody(ReviewLeaveRequestSchema, request.body);
      if ("error" in parsed) return error(reply, parsed.error, 400);
      const body = parsed.data;
      const authData = request.authData!;

      const status = body.status;
      if (
        !VALID_REVIEW_STATUSES.includes(
          status as (typeof VALID_REVIEW_STATUSES)[number],
        )
      ) {
        return error(reply, "Neplatný stav", 400);
      }

      const existing = await prisma.leave_requests.findUnique({
        where: { id },
      });
      if (!existing) return error(reply, "Žádost nenalezena", 404);

      if (existing.status !== "pending") {
        return error(
          reply,
          "Lze schválit/zamítnout pouze čekající žádosti",
          400,
        );
      }

      if (status === "approved") {
        // --- APPROVAL: create attendance records + update leave balance (matching PHP) ---
        const leaveType = existing.leave_type as string;
        const dateFrom = new Date(existing.date_from);
        const dateTo = new Date(existing.date_to);

        // For vacation: re-check balance at approval time
        if (leaveType === "vacation") {
          const year = dateFrom.getFullYear();
          const balance = await prisma.leave_balances.findFirst({
            where: { user_id: existing.user_id, year },
          });
          const vacTotal = balance ? Number(balance.vacation_total) : 160;
          const vacUsed = balance ? Number(balance.vacation_used) : 0;
          const vacRemaining = vacTotal - vacUsed;
          const totalHours = Number(existing.total_hours) || 0;
          if (totalHours > vacRemaining) {
            return error(
              reply,
              `Nedostatek dovolené. Zbývá ${vacRemaining}h, požadováno ${totalHours}h.`,
              400,
            );
          }
        }

        let totalBusinessDays = 0;
        const current = new Date(dateFrom);
        const attendanceCreates: Array<{
          user_id: number;
          shift_date: Date;
          leave_type: attendance_leave_type;
          leave_hours: number;
          notes: string;
        }> = [];

        while (current <= dateTo) {
          const dow = current.getDay();
          if (dow !== 0 && dow !== 6) {
            totalBusinessDays++;
            attendanceCreates.push({
              user_id: existing.user_id,
              shift_date: new Date(
                Date.UTC(
                  current.getFullYear(),
                  current.getMonth(),
                  current.getDate(),
                  12,
                  0,
                  0,
                ),
              ),
              leave_type: leaveType as attendance_leave_type,
              leave_hours: 8,
              notes: `Schválená žádost #${id}`,
            });
          }
          current.setDate(current.getDate() + 1);
        }

        const totalHours = totalBusinessDays * 8;

        try {
          await prisma.$transaction(async (tx) => {
            // Check for duplicate attendance records inside the transaction
            for (const ac of attendanceCreates) {
              const duplicate = await tx.attendance.findFirst({
                where: { user_id: ac.user_id, shift_date: ac.shift_date },
              });
              if (duplicate) {
                throw new Error(
                  "Pro zvolené datumy již existují záznamy docházky",
                );
              }
            }

            // 1. Create attendance records for each business day
            if (attendanceCreates.length > 0) {
              await tx.attendance.createMany({ data: attendanceCreates });
            }

            // 2. Update leave balance (vacation/sick only — not unpaid)
            if (leaveType === "vacation" || leaveType === "sick") {
              const year = dateFrom.getFullYear();
              const existingBalance = await tx.leave_balances.findFirst({
                where: { user_id: existing.user_id, year },
              });

              if (existingBalance) {
                const updateData: Record<string, unknown> = {
                  updated_at: new Date(),
                };
                if (leaveType === "vacation") {
                  updateData.vacation_used =
                    Number(existingBalance.vacation_used) + totalHours;
                } else {
                  updateData.sick_used =
                    Number(existingBalance.sick_used) + totalHours;
                }
                await tx.leave_balances.update({
                  where: { id: existingBalance.id },
                  data: updateData,
                });
              } else {
                await tx.leave_balances.create({
                  data: {
                    user_id: existing.user_id,
                    year,
                    vacation_total: 160,
                    vacation_used: leaveType === "vacation" ? totalHours : 0,
                    sick_used: leaveType === "sick" ? totalHours : 0,
                  },
                });
              }
            }

            // 3. Update request status
            await tx.leave_requests.update({
              where: { id },
              data: {
                status: "approved" as leave_requests_status,
                reviewer_id: authData.userId,
                reviewer_note: body.reviewer_note
                  ? String(body.reviewer_note)
                  : null,
                reviewed_at: new Date(),
              },
            });
          });
        } catch (e) {
          if (
            e instanceof Error &&
            e.message === "Pro zvolené datumy již existují záznamy docházky"
          ) {
            return error(
              reply,
              "Pro zvolené datumy již existují záznamy docházky",
              400,
            );
          }
          throw e;
        }

        await logAudit({
          request,
          authData,
          action: "update",
          entityType: "leave_request",
          entityId: id,
          description: `Žádost schválena — vytvořeno ${totalBusinessDays} záznamů (${totalHours}h)`,
        });
        return success(reply, { id }, 200, "Žádost byla schválena");
      }

      // --- REJECTION: just update status ---
      await prisma.leave_requests.update({
        where: { id },
        data: {
          status: "rejected" as leave_requests_status,
          reviewer_id: authData.userId,
          reviewer_note: body.reviewer_note ? String(body.reviewer_note) : null,
          reviewed_at: new Date(),
        },
      });

      await logAudit({
        request,
        authData,
        action: "update",
        entityType: "leave_request",
        entityId: id,
        description: "Žádost zamítnuta",
      });
      return success(reply, { id }, 200, "Žádost byla zamítnuta");
    },
  );

  fastify.delete<{ Params: { id: string } }>(
    "/:id",
    { preHandler: requireAuth },
    async (request, reply) => {
      const authData = request.authData!;
      const id = parseId(request.params.id, reply);
      if (id === null) return;
      const existing = await prisma.leave_requests.findUnique({
        where: { id },
      });
      if (!existing) return error(reply, "Žádost nenalezena", 404);

      if (existing.status !== "pending") {
        return error(reply, "Lze zrušit pouze čekající žádosti", 400);
      }

      if (existing.user_id !== authData.userId) {
        return error(reply, "Nemáte oprávnění zrušit tuto žádost", 403);
      }

      await prisma.leave_requests.update({
        where: { id },
        data: { status: "cancelled" },
      });
      await logAudit({
        request,
        authData: request.authData,
        action: "update",
        entityType: "leave_request",
        entityId: id,
        description: `Žádost zrušena`,
      });
      return success(reply, null, 200, "Žádost zrušena");
    },
  );
}
