import type { WeekdayAvailability } from "@capakraken/shared";
import { TRPCError } from "@trpc/server";
import {
  calculateEffectiveAvailableHours,
  calculateEffectiveBookedHours,
  calculateEffectiveDayAvailability,
  countEffectiveWorkingDays,
  loadResourceDailyAvailabilityContexts,
} from "../lib/resource-capacity.js";
import { averagePerWorkingDay, round1, toIsoDate } from "./allocation-shared.js";

export async function buildResourceAvailabilityView(
  db: Pick<import("@capakraken/db").PrismaClient, "resource" | "assignment" | "vacation" | "holidayCalendar">,
  input: {
    resourceId: string;
    startDate: Date;
    endDate: Date;
    hoursPerDay: number;
  },
) {
  const resource = await db.resource.findUnique({
    where: { id: input.resourceId },
    select: {
      id: true, displayName: true, eid: true, fte: true,
      availability: true,
      countryId: true,
      federalState: true,
      metroCityId: true,
      country: { select: { dailyWorkingHours: true, code: true } },
      metroCity: { select: { name: true } },
    },
  });
  if (!resource) {
    throw new TRPCError({ code: "NOT_FOUND", message: "Resource not found" });
  }

  const fallbackDailyHours = (resource.country?.dailyWorkingHours ?? 8) * (resource.fte ?? 1);
  const availability = (resource.availability as WeekdayAvailability | null) ?? {
    monday: fallbackDailyHours,
    tuesday: fallbackDailyHours,
    wednesday: fallbackDailyHours,
    thursday: fallbackDailyHours,
    friday: fallbackDailyHours,
    saturday: 0,
    sunday: 0,
  };

  const [existingAssignments, vacations] = await Promise.all([
    db.assignment.findMany({
      where: {
        resourceId: input.resourceId,
        status: { not: "CANCELLED" },
        startDate: { lte: input.endDate },
        endDate: { gte: input.startDate },
      },
      select: {
        id: true, startDate: true, endDate: true, hoursPerDay: true, status: true,
        project: { select: { name: true, shortCode: true } },
      },
      orderBy: { startDate: "asc" },
    }),
    db.vacation.findMany({
      where: {
        resourceId: input.resourceId,
        status: { in: ["APPROVED", "PENDING"] },
        startDate: { lte: input.endDate },
        endDate: { gte: input.startDate },
      },
      select: {
        id: true,
        type: true,
        startDate: true,
        endDate: true,
        isHalfDay: true,
        halfDayPart: true,
        status: true,
      },
      orderBy: { startDate: "asc" },
    }),
  ]);

  const contexts = await loadResourceDailyAvailabilityContexts(
    db,
    [{
      id: resource.id,
      availability,
      countryId: resource.countryId,
      countryCode: resource.country?.code,
      federalState: resource.federalState,
      metroCityId: resource.metroCityId,
      metroCityName: resource.metroCity?.name,
    }],
    input.startDate,
    input.endDate,
  );
  const context = contexts.get(resource.id);

  const totalWorkingDays = countEffectiveWorkingDays({
    availability,
    periodStart: input.startDate,
    periodEnd: input.endDate,
    context,
  });
  let availableDays = 0;
  let conflictDays = 0;
  let partialDays = 0;
  let totalAvailableHours = 0;
  const requestedHpd = input.hoursPerDay;

  const currentDate = new Date(input.startDate);
  const endDate = new Date(input.endDate);
  while (currentDate <= endDate) {
    const effectiveDayCapacity = calculateEffectiveDayAvailability({
      availability,
      date: currentDate,
      context,
    });

    if (effectiveDayCapacity > 0) {
      let bookedHours = 0;
      for (const assignment of existingAssignments) {
        bookedHours += calculateEffectiveBookedHours({
          availability,
          startDate: assignment.startDate,
          endDate: assignment.endDate,
          hoursPerDay: assignment.hoursPerDay,
          periodStart: currentDate,
          periodEnd: currentDate,
          context,
        });
      }

      const remainingCapacity = Math.max(0, effectiveDayCapacity - bookedHours);
      if (remainingCapacity >= requestedHpd) {
        availableDays++;
        totalAvailableHours += requestedHpd;
      } else if (remainingCapacity > 0) {
        partialDays++;
        totalAvailableHours += remainingCapacity;
      } else {
        conflictDays++;
      }
    }
    currentDate.setDate(currentDate.getDate() + 1);
  }

  const totalRequestedHours = totalWorkingDays * requestedHpd;
  const totalPeriodCapacity = calculateEffectiveAvailableHours({
    availability,
    periodStart: input.startDate,
    periodEnd: input.endDate,
    context,
  });
  const dailyCapacity = totalWorkingDays > 0
    ? round1(totalPeriodCapacity / totalWorkingDays)
    : 0;

  return {
    resource: { id: resource.id, name: resource.displayName, eid: resource.eid },
    dailyCapacity,
    totalWorkingDays,
    availableDays,
    partialDays,
    conflictDays,
    totalAvailableHours: round1(totalAvailableHours),
    totalRequestedHours,
    coveragePercent: totalRequestedHours > 0
      ? Math.round((totalAvailableHours / totalRequestedHours) * 100)
      : 0,
    existingAssignments: existingAssignments.map((assignment) => ({
      project: assignment.project.name,
      code: assignment.project.shortCode,
      hoursPerDay: assignment.hoursPerDay,
      start: toIsoDate(assignment.startDate),
      end: toIsoDate(assignment.endDate),
      status: assignment.status,
    })),
    vacations: vacations.map((vacation) => ({
      id: vacation.id,
      type: vacation.type,
      status: vacation.status,
      start: toIsoDate(vacation.startDate),
      end: toIsoDate(vacation.endDate),
      isHalfDay: vacation.isHalfDay,
      halfDayPart: vacation.halfDayPart,
    })),
  };
}

export function buildResourceAvailabilitySummary(
  availability: Awaited<ReturnType<typeof buildResourceAvailabilityView>>,
  period: { startDate: Date; endDate: Date },
) {
  const periodAvailableHours = availability.totalRequestedHours > 0
    ? round1(availability.dailyCapacity * availability.totalWorkingDays)
    : 0;
  const periodRemainingHours = round1(availability.totalAvailableHours);
  const periodBookedHours = round1(Math.max(0, periodAvailableHours - periodRemainingHours));

  return {
    resource: availability.resource.name,
    period: `${toIsoDate(period.startDate)} to ${toIsoDate(period.endDate)}`,
    fte: null,
    workingDays: availability.totalWorkingDays,
    periodAvailableHours,
    periodBookedHours,
    periodRemainingHours,
    maxHoursPerDay: availability.dailyCapacity,
    currentBookedHoursPerDay: round1(
      Math.max(
        0,
        availability.dailyCapacity - availability.totalAvailableHours / Math.max(availability.totalWorkingDays, 1),
      ),
    ),
    availableHoursPerDay: averagePerWorkingDay(availability.totalAvailableHours, availability.totalWorkingDays),
    isFullyAvailable: availability.existingAssignments.length === 0 && availability.vacations.length === 0,
    existingAllocations: availability.existingAssignments.map((assignment) => ({
      project: `${assignment.project} (${assignment.code})`,
      hoursPerDay: assignment.hoursPerDay,
      status: assignment.status,
      start: assignment.start,
      end: assignment.end,
    })),
    vacations: availability.vacations.map((vacation) => ({
      type: vacation.type,
      start: vacation.start,
      end: vacation.end,
      isHalfDay: vacation.isHalfDay,
    })),
  };
}
