import {
  deriveResourceForecast,
  getMonthRange,
  type AssignmentSlice,
} from "@capakraken/engine";
import type { WeekdayAvailability } from "@capakraken/shared";
import { isChargeabilityActualBooking, listAssignmentBookings } from "@capakraken/application";
import { createNotificationsForUsers } from "./create-notification.js";
import {
  calculateEffectiveAvailableHours,
  calculateEffectiveBookedHours,
  loadResourceDailyAvailabilityContexts,
} from "./resource-capacity.js";

/**
 * Minimal DB client type for chargeability alerts.
 * Uses structural typing so we can pass in `prisma as any` from the cron route.
 */
type DbClient = {
  resource: {
    findMany: (args: {
      where: Record<string, unknown>;
      select: Record<string, unknown>;
    }) => Promise<
      Array<{
        id: string;
        displayName: string;
        fte: number;
        availability: unknown;
        countryId: string | null;
        metroCityId: string | null;
        federalState: string | null;
        chargeabilityTarget: number;
        country: {
          id?: string | null;
          code: string | null;
          dailyWorkingHours: number | null;
          scheduleRules: unknown;
        } | null;
        managementLevelGroup: { targetPercentage: number | null } | null;
        metroCity: { id?: string | null; name: string | null } | null;
      }>
    >;
  };
  notification: {
    findFirst: (args: {
      where: Record<string, unknown>;
      select: { id: true };
    }) => Promise<{ id: string } | null>;
    create: (args: {
      data: {
        userId: string;
        type: string;
        category: string;
        priority: string;
        title: string;
        body: string;
        entityId: string;
        entityType: string;
        link: string;
        channel: string;
      };
    }) => Promise<{ id: string; userId: string }>;
  };
  user: {
    findMany: (args: {
      where: { systemRole: { in: string[] } };
      select: { id: true };
    }) => Promise<Array<{ id: string }>>;
  };
};

/** Alert when chargeability is more than 15pp below target */
const GAP_THRESHOLD_PP = 15;

/**
 * Find resources whose current-month chargeability is >15 percentage points
 * below their target, and create a notification for all managers.
 *
 * Duplicate-safe: skips resources that already have an alert this month.
 *
 * Returns the number of new alerts created.
 */
export async function checkChargeabilityAlerts(
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  db: any,
): Promise<number> {
  const now = new Date();
  const year = now.getUTCFullYear();
  const month = now.getUTCMonth() + 1;
  const { start: monthStart, end: monthEnd } = getMonthRange(year, month);
  const monthKey = `${year}-${String(month).padStart(2, "0")}`;

  // Fetch active, chg-responsible resources
  const resources = await (db as DbClient).resource.findMany({
    where: {
      isActive: true,
      chgResponsibility: true,
      departed: false,
      rolledOff: false,
    },
    select: {
      id: true,
      displayName: true,
      fte: true,
      availability: true,
      countryId: true,
      metroCityId: true,
      federalState: true,
      chargeabilityTarget: true,
      country: { select: { id: true, code: true, dailyWorkingHours: true, scheduleRules: true } },
      managementLevelGroup: { select: { targetPercentage: true } },
      metroCity: { select: { id: true, name: true } },
    },
  });

  if (resources.length === 0) return 0;

  const resourceIds = resources.map((r) => r.id);

  // Fetch bookings for the current month
  const allBookings = await listAssignmentBookings(db, {
    startDate: monthStart,
    endDate: monthEnd,
    resourceIds,
  });
  const availabilityContexts = await loadResourceDailyAvailabilityContexts(
    db as Parameters<typeof loadResourceDailyAvailabilityContexts>[0],
    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,
    })),
    monthStart,
    monthEnd,
  );

  // Compute chargeability per resource
  const underperformers: Array<{ resource: typeof resources[0]; chg: number; target: number; gap: number }> = [];

  for (const resource of resources) {
    const availability = resource.availability as unknown as WeekdayAvailability;
    const context = availabilityContexts.get(resource.id);
    const availableHours = calculateEffectiveAvailableHours({
      availability,
      periodStart: monthStart,
      periodEnd: monthEnd,
      context,
    });

    // Build assignment slices
    const resourceBookings = allBookings.filter(
      (b) => b.resourceId === resource.id && isChargeabilityActualBooking(b, false),
    );

    const slices: AssignmentSlice[] = resourceBookings.flatMap((b) => {
      const totalChargeableHours = calculateEffectiveBookedHours({
        availability,
        startDate: b.startDate,
        endDate: b.endDate,
        hoursPerDay: b.hoursPerDay,
        periodStart: monthStart,
        periodEnd: monthEnd,
        context,
      });
      if (totalChargeableHours <= 0) {
        return [];
      }
      return {
        hoursPerDay: b.hoursPerDay,
        workingDays: 0,
        categoryCode: "Chg", // simplified — treat all actual bookings as chargeable
        totalChargeableHours,
      };
    });

    const targetPct = resource.managementLevelGroup?.targetPercentage
      ?? (resource.chargeabilityTarget / 100);

    const forecast = deriveResourceForecast({
      fte: resource.fte,
      targetPercentage: targetPct,
      assignments: slices,
      sah: availableHours,
    });

    const chgPct = forecast.chg * 100;
    const targetPctVal = targetPct * 100;
    const gap = targetPctVal - chgPct;

    if (gap > GAP_THRESHOLD_PP) {
      underperformers.push({
        resource,
        chg: Math.round(chgPct),
        target: Math.round(targetPctVal),
        gap: Math.round(gap),
      });
    }
  }

  if (underperformers.length === 0) return 0;

  // Fetch managers to notify
  const managers = await (db as DbClient).user.findMany({
    where: { systemRole: { in: ["ADMIN", "MANAGER"] } },
    select: { id: true },
  });

  if (managers.length === 0) return 0;

  let alertCount = 0;

  for (const { resource, chg, target, gap } of underperformers) {
    // Duplicate check: one alert per resource per month
    const entityId = `chg-alert-${resource.id}-${monthKey}`;
    const existing = await (db as DbClient).notification.findFirst({
      where: {
        entityId,
        entityType: "chargeability_alert",
        type: "CHARGEABILITY_ALERT",
      },
      select: { id: true },
    });

    if (existing) continue;

    await createNotificationsForUsers({
      db: db as DbClient,
      userIds: managers.map((m) => m.id),
      type: "CHARGEABILITY_ALERT",
      category: "NOTIFICATION",
      priority: "HIGH",
      title: `Low chargeability: ${resource.displayName}`,
      body: `${resource.displayName} is at ${chg}% chargeability this month (target: ${target}%, gap: ${gap}pp).`,
      entityId,
      entityType: "chargeability_alert",
      link: "/chargeability",
      channel: "in_app",
    });

    alertCount++;
  }

  return alertCount;
}
