import { db } from "@/lib/db";
import { notify } from "@/lib/notify";
import {
  createLeaveApprovals,
  ensureLeaveApprovals,
  resolveLeaveRequestStatus,
  userCanProcessLeave,
} from "@/lib/leave-approval";
import type { LeaveRequestInput, LeaveRequestActionInput } from "./schema";
import type { LeaveRequestListItem, LeaveRequestWithRelations } from "./types";
import type { PrismaClient } from "@prisma/client";

const prisma = db as PrismaClient;

// ─── Helpers ──────────────────────────────────────────────────────────────────

function calculateDuration(startDate: Date, endDate: Date): number {
  const diffTime = endDate.getTime() - startDate.getTime();
  const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
  return Math.max(0, diffDays);
}

/** Safely serialize a Date (or already-string) to "YYYY-MM-DD" */
function toDateStr(d: Date | string): string {
  if (d instanceof Date) return d.toISOString().split("T")[0];
  if (typeof d === "string" && d.includes("T")) return d.split("T")[0];
  return String(d);
}

// ─── Leave Requests ───────────────────────────────────────────────────────────

export async function listLeaveRequests(
  currentUserId?: string,
): Promise<LeaveRequestListItem[]> {
  const requests = await prisma.leaveRequest.findMany({
    orderBy: { createdAt: "desc" },
    include: {
      employee: {
        select: {
          id: true,
          fullName: true,
          userId: true,
          department: {
            select: {
              id: true,
              name: true,
              branch: {
                select: {
                  id: true,
                  name: true,
                },
              },
            },
          },
        },
      },
      leaveType: { select: { id: true, name: true } },
      approvals: {
        include: {
          approver: { select: { id: true, name: true } },
        },
        orderBy: { createdAt: "asc" },
      },
    },
  });

  // Backfill approvals for pending legacy requests
  await Promise.all(
    requests
      .filter((r) => r.status === "pending" && r.approvals.length === 0)
      .map((r) => ensureLeaveApprovals(r.id, r.employee.userId)),
  );

  // Re-fetch if any were backfilled
  const needsRefetch = requests.some(
    (r) => r.status === "pending" && r.approvals.length === 0,
  );
  const rows = needsRefetch
    ? await prisma.leaveRequest.findMany({
        orderBy: { createdAt: "desc" },
        include: {
          employee: {
            select: {
              id: true,
              fullName: true,
              userId: true,
              department: {
                select: {
                  id: true,
                  name: true,
                  branch: {
                    select: { id: true, name: true },
                  },
                },
              },
            },
          },
          leaveType: { select: { id: true, name: true } },
          approvals: {
            include: {
              approver: { select: { id: true, name: true } },
            },
            orderBy: { createdAt: "asc" },
          },
        },
      })
    : requests;

  return rows.map((r) => {
    const approved = r.approvals.filter((a) => a.status === "approved").length;
    const rejected = r.approvals.filter((a) => a.status === "rejected").length;
    const pending = r.approvals.filter((a) => a.status === "pending").length;
    const myApproval = currentUserId
      ? r.approvals.find((a) => a.approverId === currentUserId)
      : undefined;

    return {
      id: r.id,
      employeeName: r.employee.fullName,
      leaveTypeName: r.leaveType.name,
      startDate: toDateStr(r.startDate),
      endDate: toDateStr(r.endDate),
      duration: r.duration,
      status: r.status as "pending" | "approved" | "rejected",
      createdAt: toDateStr(r.createdAt),
      branchName: r.employee.department?.branch?.name ?? null,
      departmentName: r.employee.department?.name ?? null,
      approvalTotal: r.approvals.length,
      approvalApproved: approved,
      approvalRejected: rejected,
      approvalPending: pending,
      myApprovalStatus: myApproval
        ? (myApproval.status as "pending" | "approved" | "rejected")
        : null,
      approvals: r.approvals.map((a) => ({
        approverName: a.approver.name,
        role: a.role,
        status: a.status as "pending" | "approved" | "rejected",
        reason: a.reason,
        actedAt: a.actedAt ? toDateStr(a.actedAt) : null,
      })),
    };
  });
}

export async function getLeaveRequestById(
  id: string,
): Promise<LeaveRequestWithRelations | null> {
  return prisma.leaveRequest.findUnique({
    where: { id },
    include: {
      employee: { select: { id: true, fullName: true, email: true } },
      leaveType: { select: { id: true, name: true } },
      approvedBy: { select: { id: true, name: true } },
    },
  }) as Promise<LeaveRequestWithRelations | null>;
}

export async function createLeaveRequest(
  data: LeaveRequestInput & { duration?: number },
) {
  const startDate = data.startDate as Date;
  const endDate = data.endDate as Date;
  const duration = data.duration ?? calculateDuration(startDate, endDate);

  const leaveRequest = await prisma.leaveRequest.create({
    data: {
      employeeId: data.employeeId,
      leaveTypeId: data.leaveTypeId,
      startDate,
      endDate,
      duration,
      reason: data.reason,
      status: "pending",
    },
    include: {
      employee: {
        select: {
          fullName: true,
          userId: true,
          reportingManagerId: true,
          reportingManager: {
            select: {
              userId: true,
            },
          },
        },
      },
      leaveType: { select: { name: true } },
    },
  });

  // Create one pending approval per user with leave "process" permission
  await createLeaveApprovals(
    leaveRequest.id,
    leaveRequest.employee.userId ?? undefined,
  );

  // Send notification
  await notify("leave.request.submitted", {
    employeeName: leaveRequest.employee.fullName,
    leaveType: leaveRequest.leaveType.name,
    startDate: toDateStr(leaveRequest.startDate),
    endDate: toDateStr(leaveRequest.endDate),
    duration: leaveRequest.duration,
    employeeUserId: leaveRequest.employee.userId ?? undefined,
    managerId: leaveRequest.employee.reportingManager?.userId ?? undefined,
    triggeredByUserId: leaveRequest.employee.userId ?? undefined,
  });

  return leaveRequest;
}

export async function updateLeaveRequest(id: string, data: LeaveRequestInput) {
  const existing = await prisma.leaveRequest.findUnique({ where: { id } });
  if (!existing) throw new Error("Leave request not found");
  if (existing.status !== "pending") {
    throw new Error("Cannot update a request that is not pending");
  }

  const startDate = data.startDate as Date;
  const endDate = data.endDate as Date;
  const duration = calculateDuration(startDate, endDate);

  const updated = await prisma.leaveRequest.update({
    where: { id },
    data: {
      employeeId: data.employeeId,
      leaveTypeId: data.leaveTypeId,
      startDate,
      endDate,
      duration,
      reason: data.reason,
    },
    include: {
      employee: { select: { fullName: true, userId: true } },
      leaveType: { select: { name: true } },
    },
  });

  // Send notification about update
  await notify("leave.request.updated", {
    employeeName: updated.employee.fullName,
    leaveType: updated.leaveType.name,
    startDate: toDateStr(updated.startDate),
    endDate: toDateStr(updated.endDate),
    triggeredByUserId: updated.employee.userId ?? undefined,
  });

  return updated;
}

export async function deleteLeaveRequest(id: string) {
  const existing = await prisma.leaveRequest.findUnique({
    where: { id },
    include: {
      employee: { select: { fullName: true, userId: true } },
      leaveType: { select: { name: true } },
    },
  });
  if (!existing) throw new Error("Leave request not found");
  if (existing.status !== "pending") {
    throw new Error("Cannot delete a request that is not pending");
  }

  await prisma.leaveRequest.delete({ where: { id } });

  // Send notification about cancellation
  await notify("leave.request.cancelled", {
    employeeName: existing.employee.fullName,
    leaveType: existing.leaveType.name,
    triggeredByUserId: existing.employee.userId ?? undefined,
  });
}

export async function processLeaveRequest(
  id: string,
  data: LeaveRequestActionInput,
  approverId: string,
) {
  const canProcess = await userCanProcessLeave(approverId);
  if (!canProcess) {
    throw new Error("You do not have permission to process leave requests");
  }

  const existing = await prisma.leaveRequest.findUnique({
    where: { id },
    include: {
      employee: { select: { userId: true } },
      approvals: true,
    },
  });
  if (!existing) throw new Error("Leave request not found");
  if (existing.status !== "pending") {
    throw new Error("Request has already been processed");
  }

  // Ensure approval rows exist (legacy requests)
  if (existing.approvals.length === 0) {
    await ensureLeaveApprovals(id, existing.employee.userId);
  }

  const approval = await prisma.leaveApproval.findUnique({
    where: {
      leaveRequestId_approverId: {
        leaveRequestId: id,
        approverId,
      },
    },
  });
  if (!approval) {
    throw new Error("You are not assigned as an approver for this request");
  }
  if (approval.status !== "pending") {
    throw new Error("You have already acted on this request");
  }

  const approvalStatus = data.action === "approve" ? "approved" : "rejected";
  await prisma.leaveApproval.update({
    where: { id: approval.id },
    data: {
      status: approvalStatus,
      reason: data.action === "reject" ? data.reason || null : null,
      actedAt: new Date(),
    },
  });

  const allApprovals = await prisma.leaveApproval.findMany({
    where: { leaveRequestId: id },
  });
  const finalStatus = resolveLeaveRequestStatus(allApprovals);

  const approver = await prisma.user.findUnique({
    where: { id: approverId },
    select: { name: true },
  });

  let updated;
  if (finalStatus === "pending") {
    updated = await prisma.leaveRequest.findUnique({
      where: { id },
      include: {
        employee: { select: { fullName: true, userId: true } },
        leaveType: { select: { name: true } },
        approvedBy: { select: { name: true } },
        approvals: {
          include: { approver: { select: { name: true } } },
        },
      },
    });
  } else {
    const rejectionReason =
      finalStatus === "rejected"
        ? data.action === "reject"
          ? data.reason || null
          : allApprovals.find((a) => a.status === "rejected")?.reason ?? null
        : null;

    updated = await prisma.leaveRequest.update({
      where: { id },
      data: {
        status: finalStatus,
        approvedById: approverId,
        approvedAt: new Date(),
        rejectionReason,
      },
      include: {
        employee: { select: { fullName: true, userId: true } },
        leaveType: { select: { name: true } },
        approvedBy: { select: { name: true } },
        approvals: {
          include: { approver: { select: { name: true } } },
        },
      },
    });

    // Notify employee only when the request reaches a final state
    if (finalStatus === "approved") {
      await notify("leave.request.approved", {
        employeeUserId: updated!.employee.userId ?? undefined,
        leaveType: updated!.leaveType.name,
        startDate: toDateStr(updated!.startDate),
        endDate: toDateStr(updated!.endDate),
        duration: updated!.duration,
        approvedBy: approver?.name ?? "Admin",
        triggeredByUserId: approverId,
      });
    } else {
      await notify("leave.request.rejected", {
        employeeUserId: updated!.employee.userId ?? undefined,
        leaveType: updated!.leaveType.name,
        startDate: toDateStr(updated!.startDate),
        endDate: toDateStr(updated!.endDate),
        reason: rejectionReason,
        triggeredByUserId: approverId,
      });
    }
  }

  return updated;
}

// ─── Public Holidays ──────────────────────────────────────────────────────────

export async function listPublicHolidays() {
  return prisma.publicHoliday.findMany({
    orderBy: { date: "asc" },
  });
}

export async function createPublicHoliday(data: {
  name: string;
  type: string;
  description?: string;
  date: string;
  endDate?: string;
}) {
  return prisma.publicHoliday.create({
    data: {
      name: data.name.trim(),
      type: data.type,
      description: data.description?.trim() || null,
      date: new Date(data.date),
      endDate: data.endDate ? new Date(data.endDate) : null,
    },
  });
}

export async function updatePublicHoliday(
  id: string,
  data: {
    name?: string;
    type?: string;
    description?: string;
    date?: string;
    endDate?: string | null;
  },
) {
  const existing = await prisma.publicHoliday.findUnique({ where: { id } });
  if (!existing) throw new Error("Holiday not found");

  return prisma.publicHoliday.update({
    where: { id },
    data: {
      ...(data.name !== undefined && { name: data.name.trim() }),
      ...(data.type !== undefined && { type: data.type }),
      ...(data.description !== undefined && {
        description: data.description?.trim() || null,
      }),
      ...(data.date !== undefined && { date: new Date(data.date) }),
      ...(data.endDate !== undefined && {
        endDate: data.endDate ? new Date(data.endDate) : null,
      }),
    },
  });
}

export async function deletePublicHoliday(id: string) {
  const existing = await prisma.publicHoliday.findUnique({ where: { id } });
  if (!existing) throw new Error("Holiday not found");

  await prisma.publicHoliday.delete({ where: { id } });
}

export async function listLeaveRequestsForAttendance(filters: {
  dateFrom?: string;
  dateTo?: string;
  status?: string;
}) {
  const where: Record<string, unknown> = {};

  if (filters.status) {
    where.status = filters.status;
  }

  if (filters.dateFrom || filters.dateTo) {
    where.AND = [
      ...(filters.dateTo
        ? [{ startDate: { lte: new Date(filters.dateTo) } }]
        : []),
      ...(filters.dateFrom
        ? [{ endDate: { gte: new Date(filters.dateFrom) } }]
        : []),
    ];
  }

  const rows = await prisma.leaveRequest.findMany({
    where,
    select: {
      employeeId: true,
      startDate: true,
      endDate: true,
      status: true,
    },
    orderBy: { startDate: "asc" },
  });

  return rows.map((r) => ({
    employeeId: r.employeeId,
    startDate: toDateStr(r.startDate),
    endDate: toDateStr(r.endDate),
    status: r.status,
  }));
}
