import {
  DayPeriod,
  LeaveRequestStatus,
  SystemRole,
} from '@prisma/client';
import { prisma } from '../db';
import { badRequest } from '../lib/errors';

export function leaveDaysBetween(
  fromDate: string,
  toDate: string,
  dayPeriod: string
): number {
  if (!fromDate || !toDate || toDate < fromDate) return 0;
  if (fromDate === toDate) {
    if (dayPeriod === 'morning' || dayPeriod === 'afternoon') return 0.5;
    return 1;
  }
  const a = new Date(fromDate + 'T12:00:00');
  const b = new Date(toDate + 'T12:00:00');
  const diff = Math.round((b.getTime() - a.getTime()) / 86400000) + 1;
  return Math.max(0, diff);
}

export function normalizeDayPeriod(
  fromDate: string,
  toDate: string,
  dayPeriod: string
): DayPeriod {
  if (fromDate !== toDate) return DayPeriod.full;
  if (dayPeriod === 'morning') return DayPeriod.morning;
  if (dayPeriod === 'afternoon') return DayPeriod.afternoon;
  return DayPeriod.full;
}

export async function computeBalance(
  employeeId: string,
  typeId: string,
  year: number
) {
  const type = await prisma.leaveType.findUnique({ where: { id: typeId } });
  if (!type || !type.tracksBalance) {
    return {
      typeId,
      allocated: 0,
      used: 0,
      pending: 0,
      available: null as number | null,
      validUntil: null as string | null,
      tracksBalance: false,
    };
  }

  const alloc = await prisma.leaveAllocation.findUnique({
    where: {
      employeeId_typeId_year: { employeeId, typeId, year },
    },
  });
  const allocated = alloc ? Number(alloc.allocated) : 0;

  const requests = await prisma.leaveRequest.findMany({
    where: {
      employeeId,
      typeId,
      status: { in: [LeaveRequestStatus.to_approve, LeaveRequestStatus.validated] },
    },
  });

  let used = 0;
  let pending = 0;
  for (const r of requests) {
    const y = r.fromDate.getUTCFullYear();
    if (y !== year) continue;
    const days = Number(r.days);
    if (r.status === LeaveRequestStatus.validated) used += days;
    else pending += days;
  }

  const available = Math.round((allocated - used - pending) * 100) / 100;
  return {
    typeId,
    allocated,
    used,
    pending,
    available,
    validUntil: alloc?.validUntil
      ? alloc.validUntil.toISOString().slice(0, 10)
      : null,
    tracksBalance: true,
  };
}

export async function assertCanApply(opts: {
  employeeId: string;
  typeId: string;
  fromDate: string;
  toDate: string;
  dayPeriod: string;
  year: number;
}) {
  if (opts.toDate < opts.fromDate) {
    throw badRequest('Invalid date range');
  }
  if (
    opts.fromDate !== opts.toDate &&
    (opts.dayPeriod === 'morning' || opts.dayPeriod === 'afternoon')
  ) {
    throw badRequest('Half-day period is only allowed when From and To are the same day');
  }

  const type = await prisma.leaveType.findUnique({ where: { id: opts.typeId } });
  if (!type) throw badRequest('Invalid leave type');

  const period = normalizeDayPeriod(opts.fromDate, opts.toDate, opts.dayPeriod);
  const days = leaveDaysBetween(opts.fromDate, opts.toDate, period);
  if (!(days > 0)) throw badRequest('Choose a valid date range');

  if (type.tracksBalance) {
    const bal = await computeBalance(opts.employeeId, opts.typeId, opts.year);
    if (bal.available != null && days > bal.available) {
      throw badRequest(
        `Insufficient balance (${bal.available} days available)`,
        { available: bal.available, requested: days }
      );
    }
  }

  return { type, days, period };
}

export async function notifyEmployee(employeeId: string, text: string) {
  await prisma.notification.create({
    data: { employeeId, text, read: false },
  });
}

export async function notifySuperAdmins(text: string) {
  const admins = await prisma.employee.findMany({
    where: { role: SystemRole.SuperAdmin, active: true },
    select: { id: true },
  });
  if (!admins.length) return;
  await prisma.notification.createMany({
    data: admins.map((a) => ({
      employeeId: a.id,
      text,
      read: false,
    })),
  });
}
