import {
  isChargeabilityActualBooking,
  isChargeabilityRelevantProject,
  listAssignmentBookings,
} from "@capakraken/application";
import type { WeekdayAvailability } from "@capakraken/shared";
import { z } from "zod";
import {
  anonymizeResource,
  getAnonymizationDirectory,
} from "../lib/anonymization.js";
import {
  calculateEffectiveAvailableHours,
  calculateEffectiveBookedHours,
  calculateEffectiveDayAvailability,
  loadResourceDailyAvailabilityContexts,
} from "../lib/resource-capacity.js";
import { controllerProcedure } from "../trpc.js";
import { buildDailyBookedHoursMap } from "./resource-capacity-shared.js";

export const resourceCapacityReadProcedures = {
  listWithUtilization: controllerProcedure
    .input(
      z.object({
        startDate: z.string().datetime().optional(),
        endDate: z.string().datetime().optional(),
        chapter: z.string().optional(),
        includeProposed: z.boolean().default(false),
        limit: z.number().int().min(1).max(500).default(100),
      }),
    )
    .query(async ({ ctx, input }) => {
      const now = new Date();
      const start = input.startDate ? new Date(input.startDate) : new Date(now.getFullYear(), now.getMonth(), 1);
      const end = input.endDate ? new Date(input.endDate) : new Date(now.getFullYear(), now.getMonth() + 3, 0);

      const resources = await ctx.db.resource.findMany({
        where: {
          isActive: true,
          ...(input.chapter ? { chapter: input.chapter } : {}),
        },
        take: input.limit,
        orderBy: { displayName: "asc" },
        select: {
          id: true,
          eid: true,
          displayName: true,
          email: true,
          chapter: true,
          lcrCents: true,
          ucrCents: true,
          currency: true,
          chargeabilityTarget: true,
          availability: true,
          skills: true,
          dynamicFields: true,
          blueprintId: true,
          isActive: true,
          createdAt: true,
          updatedAt: true,
          roleId: true,
          portfolioUrl: true,
          postalCode: true,
          federalState: true,
          countryId: true,
          metroCityId: true,
          valueScore: true,
          valueScoreBreakdown: true,
          valueScoreUpdatedAt: true,
          userId: true,
          country: { select: { code: true } },
          metroCity: { select: { name: true } },
        },
      });
      const bookings = await listAssignmentBookings(ctx.db, {
        startDate: start,
        endDate: end,
        resourceIds: resources.map((resource) => resource.id),
      });
      const bookingsByResourceId = new Map<string, typeof bookings>();
      for (const booking of bookings) {
        if (!booking.resourceId) {
          continue;
        }
        const items = bookingsByResourceId.get(booking.resourceId) ?? [];
        items.push(booking);
        bookingsByResourceId.set(booking.resourceId, items);
      }
      const contexts = await loadResourceDailyAvailabilityContexts(
        ctx.db,
        resources.map((resource) => ({
          id: resource.id,
          availability: resource.availability as unknown as WeekdayAvailability,
          countryId: resource.countryId,
          countryCode: resource.country?.code,
          federalState: resource.federalState,
          metroCityId: resource.metroCityId,
          metroCityName: resource.metroCity?.name,
        })),
        start,
        end,
      );
      const directory = await getAnonymizationDirectory(ctx.db);

      return resources.map((resource) => {
        const availability = resource.availability as unknown as WeekdayAvailability;
        const context = contexts.get(resource.id);
        const resourceBookings = (bookingsByResourceId.get(resource.id) ?? []).filter(
          (booking) =>
            booking.resourceId === resource.id &&
            (input.includeProposed || booking.status !== "PROPOSED"),
        );
        const availableHours = calculateEffectiveAvailableHours({
          availability,
          periodStart: start,
          periodEnd: end,
          context,
        });
        const bookedHours = resourceBookings.reduce(
          (sum, booking) => sum + calculateEffectiveBookedHours({
            availability,
            startDate: booking.startDate,
            endDate: booking.endDate,
            hoursPerDay: booking.hoursPerDay,
            periodStart: start,
            periodEnd: end,
            context,
          }),
          0,
        );
        const dailyBookedHours = buildDailyBookedHoursMap(resourceBookings, availability, context, start, end);
        const isOverbooked = Array.from(dailyBookedHours.entries()).some(([isoDate, hours]) => {
          const date = new Date(`${isoDate}T00:00:00.000Z`);
          const dayCapacity = calculateEffectiveDayAvailability({
            availability,
            date,
            context,
          });
          return dayCapacity > 0 && hours > dayCapacity;
        });

        const utilizationPercent = availableHours > 0 ? Math.round((bookedHours / availableHours) * 100) : 0;

        return anonymizeResource({
          ...resource,
          bookingCount: resourceBookings.length,
          bookedHours: Math.round(bookedHours),
          availableHours: Math.round(availableHours),
          utilizationPercent,
          isOverbooked,
        }, directory);
      });
    }),

  getChargeabilityStats: controllerProcedure
    .input(z.object({ includeProposed: z.boolean().default(false), resourceId: z.string().optional() }))
    .query(async ({ ctx, input }) => {
      const now = new Date();
      const start = new Date(now.getFullYear(), now.getMonth(), 1);
      const end = new Date(now.getFullYear(), now.getMonth() + 1, 0);

      const resources = await ctx.db.resource.findMany({
        where: {
          isActive: true,
          ...(input.resourceId ? { id: input.resourceId } : {}),
        },
        select: {
          id: true,
          eid: true,
          displayName: true,
          chapter: true,
          chargeabilityTarget: true,
          availability: true,
          countryId: true,
          federalState: true,
          metroCityId: true,
          country: { select: { code: true } },
          metroCity: { select: { name: true } },
        },
      });
      const bookings = await listAssignmentBookings(ctx.db, {
        startDate: start,
        endDate: end,
        resourceIds: resources.map((resource) => resource.id),
      });
      const contexts = await loadResourceDailyAvailabilityContexts(
        ctx.db,
        resources.map((resource) => ({
          id: resource.id,
          availability: resource.availability as unknown as WeekdayAvailability,
          countryId: resource.countryId,
          countryCode: resource.country?.code,
          federalState: resource.federalState,
          metroCityId: resource.metroCityId,
          metroCityName: resource.metroCity?.name,
        })),
        start,
        end,
      );
      const directory = await getAnonymizationDirectory(ctx.db);

      return resources.map((resource) => {
        const availability = resource.availability as unknown as WeekdayAvailability;
        const context = contexts.get(resource.id);
        const resourceBookings = bookings.filter((booking) => booking.resourceId === resource.id);

        const actualAllocs = resourceBookings.filter((booking) =>
          isChargeabilityActualBooking(booking, input.includeProposed),
        );
        const expectedAllocs = resourceBookings.filter((booking) =>
          isChargeabilityRelevantProject(booking.project, true),
        );

        const availableHours = calculateEffectiveAvailableHours({
          availability,
          periodStart: start,
          periodEnd: end,
          context,
        });
        const actualBookedHours = actualAllocs.reduce(
          (sum, booking) => sum + calculateEffectiveBookedHours({
            availability,
            startDate: booking.startDate,
            endDate: booking.endDate,
            hoursPerDay: booking.hoursPerDay,
            periodStart: start,
            periodEnd: end,
            context,
          }),
          0,
        );
        const expectedBookedHours = expectedAllocs.reduce(
          (sum, booking) => sum + calculateEffectiveBookedHours({
            availability,
            startDate: booking.startDate,
            endDate: booking.endDate,
            hoursPerDay: booking.hoursPerDay,
            periodStart: start,
            periodEnd: end,
            context,
          }),
          0,
        );

        const actualChargeability = availableHours > 0
          ? Math.round((actualBookedHours / availableHours) * 100)
          : 0;
        const expectedChargeability = availableHours > 0
          ? Math.round((expectedBookedHours / availableHours) * 100)
          : 0;

        return anonymizeResource({
          id: resource.id,
          eid: resource.eid,
          displayName: resource.displayName,
          chapter: resource.chapter,
          chargeabilityTarget: resource.chargeabilityTarget,
          actualChargeability,
          expectedChargeability,
          availableHours: Math.round(availableHours),
        }, directory);
      });
    }),
};
