import type { PrismaClient } from "@nexus/db";
import type { WeeklyDigestData } from "./weekly-digest-template.js";
import { sendEmail } from "./email.js";
import { buildWeeklyDigestHtml, buildWeeklyDigestText } from "./weekly-digest-template.js";
import { getAppBaseUrl } from "./app-base-url.js";
import { listAssignmentBookings } from "@nexus/application";
import {
  calculateEffectiveAvailableHours,
  calculateEffectiveBookedHours,
  loadResourceDailyAvailabilityContexts,
} from "./resource-capacity.js";
import type { WeekdayAvailability } from "@nexus/shared";

type DbClient = Pick<
  PrismaClient,
  "user" | "resource" | "demandRequirement" | "vacation" | "assignment" | "holidayCalendar"
>;

function addDays(d: Date, days: number): Date {
  const next = new Date(d);
  next.setDate(next.getDate() + days);
  return next;
}

function isoDate(d: Date): string {
  return d.toISOString().slice(0, 10);
}

function weekLabel(start: Date, end: Date): string {
  const fmt = (d: Date) => d.toLocaleDateString("en-GB", { day: "numeric", month: "short" });
  return `${fmt(start)} – ${fmt(end)}`;
}

export async function sendWeeklyDigest(db: DbClient): Promise<{ sent: number; skipped: number }> {
  const today = new Date();
  today.setHours(0, 0, 0, 0);
  const periodStart = today;
  const periodEnd = addDays(today, 27); // next 4 weeks

  // Collect recipients: ADMIN + MANAGER users
  const recipients = await db.user.findMany({
    where: { systemRole: { in: ["ADMIN", "MANAGER"] } as Record<string, unknown> },
    select: { id: true, email: true, name: true },
  });

  if (recipients.length === 0) {
    return { sent: 0, skipped: 0 };
  }

  // Load active resources
  const resources = await db.resource.findMany({
    where: { isActive: true },
    select: {
      id: true,
      displayName: true,
      availability: true,
      countryId: true,
      federalState: true,
      metroCityId: true,
      country: { select: { code: true } },
      metroCity: { select: { name: true } },
    },
    take: 200,
  });

  // Compute utilization for each resource
  const availabilityContexts = await loadResourceDailyAvailabilityContexts(
    db,
    resources.map((r) => ({
      id: r.id,
      availability: r.availability as unknown as WeekdayAvailability,
      countryId: r.countryId,
      countryCode: r.country?.code,
      federalState: r.federalState,
      metroCityId: r.metroCityId,
      metroCityName: r.metroCity?.name,
    })),
    periodStart,
    periodEnd,
  );

  const bookings = await listAssignmentBookings(db, {
    startDate: periodStart,
    endDate: periodEnd,
    resourceIds: resources.map((r) => r.id),
  });
  const bookingsByResource = new Map<string, typeof bookings>();
  for (const b of bookings) {
    if (!b.resourceId) continue;
    const arr = bookingsByResource.get(b.resourceId) ?? [];
    arr.push(b);
    bookingsByResource.set(b.resourceId, arr);
  }

  let totalAvailable = 0;
  let totalBooked = 0;
  let overbookedCount = 0;
  const resourceUtils: { name: string; utilizationPct: number }[] = [];

  for (const r of resources) {
    const availability = r.availability as unknown as WeekdayAvailability;
    const context = availabilityContexts.get(r.id);
    const available = calculateEffectiveAvailableHours({
      availability,
      periodStart,
      periodEnd,
      context,
    });
    const booked = (bookingsByResource.get(r.id) ?? []).reduce(
      (sum, b) =>
        sum +
        calculateEffectiveBookedHours({
          availability,
          startDate: b.startDate,
          endDate: b.endDate,
          hoursPerDay: b.hoursPerDay,
          periodStart,
          periodEnd,
          context,
        }),
      0,
    );
    totalAvailable += available;
    totalBooked += booked;
    if (booked > available + 0.5) overbookedCount++;
    if (available > 0) {
      resourceUtils.push({
        name: r.displayName,
        utilizationPct: Math.min(100, (booked / available) * 100),
      });
    }
  }

  const teamUtilizationPct =
    totalAvailable > 0 ? Math.min(100, (totalBooked / totalAvailable) * 100) : 0;
  const topResources = resourceUtils
    .sort((a, b) => b.utilizationPct - a.utilizationPct)
    .slice(0, 5);

  // Count open demands (unfilled demand requirements)
  const openDemandCount = await db.demandRequirement.count({
    where: {
      endDate: { gte: today },
      assignments: { none: {} },
    },
  });

  // Count upcoming vacations (next 14 days)
  const vacationEnd = addDays(today, 14);
  const upcomingVacationCount = await db.vacation.count({
    where: {
      startDate: { lte: vacationEnd },
      endDate: { gte: today },
    },
  });

  const appBaseUrl = getAppBaseUrl();
  const digestData: WeeklyDigestData = {
    weekLabel: weekLabel(periodStart, periodEnd),
    teamUtilizationPct,
    overbookedCount,
    openDemandCount,
    upcomingVacationCount,
    topResources,
    appBaseUrl,
  };

  const html = buildWeeklyDigestHtml(digestData);
  const text = buildWeeklyDigestText(digestData);
  const subject = `Nexus Weekly Digest — ${isoDate(today)}`;

  let sent = 0;
  let skipped = 0;
  for (const user of recipients) {
    const ok = await sendEmail({ to: user.email, subject, html, text });
    if (ok) sent++;
    else skipped++;
  }

  return { sent, skipped };
}
