import { listAssignmentBookings } from "@capakraken/application";
import { rankResources } from "@capakraken/staffing";
import type { SkillEntry, WeekdayAvailability } from "@capakraken/shared";
import {
  calculateEffectiveAvailableHours,
  calculateEffectiveBookedHours,
  loadResourceDailyAvailabilityContexts,
} from "./resource-capacity.js";
import { createNotificationsForUsers } from "./create-notification.js";

/**
 * Minimal DB interface for auto-staffing — avoids importing the full PrismaClient.
 * Follows the same pattern as budget-alerts.ts.
 */
type DbClient = Parameters<typeof listAssignmentBookings>[0] & {
  demandRequirement: {
    findUnique: (args: {
      where: { id: string };
      select: {
        id: true;
        projectId: true;
        startDate: true;
        endDate: true;
        hoursPerDay: true;
        role: true;
        roleId: true;
        headcount: true;
        budgetCents: true;
      };
    }) => Promise<{
      id: string;
      projectId: string;
      startDate: Date;
      endDate: Date;
      hoursPerDay: number;
      role: string | null;
      roleId: string | null;
      headcount: number;
      budgetCents: number;
    } | null>;
  };
  project: {
    findUnique: (args: {
      where: { id: string };
      select: { id: true; name: true };
    }) => Promise<{ id: string; name: string } | null>;
  };
  role: {
    findUnique: (args: {
      where: { id: string };
      select: { id: true; name: true };
    }) => Promise<{ id: string; name: string } | null>;
  };
  resource: {
    findMany: (args: {
      where: { isActive: true };
    }) => Promise<Array<{
      id: string;
      displayName: string;
      eid: string | null;
      skills: unknown;
      lcrCents: number;
      chargeabilityTarget: number;
      availability: unknown;
      valueScore: number | null;
      countryId: string | null;
      federalState: string | null;
      metroCityId: string | null;
      country: { code: string | null } | null;
      metroCity: { name: string | null } | null;
    }>>;
  };
  notification: {
    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 }>>;
  };
};

const TOP_N = 3;

/**
 * Generate automatic staffing suggestions for a demand requirement.
 *
 * Fetches the demand's role/dates/hours, runs the staffing ranking algorithm
 * for the top 3 matches, and creates a notification for project managers
 * with a summary of the suggestions.
 *
 * This function is designed to be called fire-and-forget (non-blocking).
 * It swallows all errors to avoid disrupting the caller.
 */
export async function generateAutoSuggestions(
  db: DbClient,
  demandRequirementId: string,
): Promise<void> {
  try {
    // 1. Load the demand requirement
    const demand = await db.demandRequirement.findUnique({
      where: { id: demandRequirementId },
      select: {
        id: true,
        projectId: true,
        startDate: true,
        endDate: true,
        hoursPerDay: true,
        role: true,
        roleId: true,
        headcount: true,
        budgetCents: true,
      },
    });

    if (!demand) return;

    // 2. Resolve project and role names
    const [project, roleEntity] = await Promise.all([
      db.project.findUnique({
        where: { id: demand.projectId },
        select: { id: true, name: true },
      }),
      demand.roleId
        ? db.role.findUnique({
            where: { id: demand.roleId },
            select: { id: true, name: true },
          })
        : Promise.resolve(null),
    ]);

    if (!project) return;

    const roleName = roleEntity?.name ?? demand.role ?? "Unspecified role";

    // 3. Derive required skills from role name
    // The role name itself is treated as the primary required skill.
    // Resources with matching skill names in their skill matrix will rank highest.
    const requiredSkills = [roleName];

    // 4. Fetch all active resources and their current bookings
    const resources = await db.resource.findMany({
      where: { isActive: true },
    });

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

    const bookings = await listAssignmentBookings(db, {
      startDate: demand.startDate,
      endDate: demand.endDate,
      resourceIds: resources.map((r) => r.id),
    });
    const contexts = 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,
      })),
      demand.startDate,
      demand.endDate,
    );

    // 5. Enrich resources with utilization data for the demand's date range
    const enrichedResources = resources.map((resource) => {
      const availability = resource.availability as unknown as WeekdayAvailability;
      const context = contexts.get(resource.id);
      const resourceBookings = bookings.filter((b) => b.resourceId === resource.id);
      const totalAvailableHours = calculateEffectiveAvailableHours({
        availability,
        periodStart: demand.startDate,
        periodEnd: demand.endDate,
        context,
      });
      const allocatedHours = resourceBookings.reduce(
        (sum, booking) =>
          sum + calculateEffectiveBookedHours({
            availability,
            startDate: booking.startDate,
            endDate: booking.endDate,
            hoursPerDay: booking.hoursPerDay,
            periodStart: demand.startDate,
            periodEnd: demand.endDate,
            context,
          }),
        0,
      );

      const utilizationPercent =
        totalAvailableHours > 0
          ? Math.min(100, (allocatedHours / totalAvailableHours) * 100)
          : 0;

      const wouldExceedCapacity = totalAvailableHours > 0
        ? allocatedHours + demand.hoursPerDay > totalAvailableHours
        : demand.hoursPerDay > 0;

      return {
        id: resource.id,
        displayName: resource.displayName,
        eid: resource.eid,
        skills: resource.skills as unknown as SkillEntry[],
        lcrCents: resource.lcrCents,
        chargeabilityTarget: resource.chargeabilityTarget,
        currentUtilizationPercent: utilizationPercent,
        hasAvailabilityConflicts: wouldExceedCapacity,
        conflictDays: wouldExceedCapacity ? ["(multiple days)"] : [],
        valueScore: resource.valueScore ?? 0,
      };
    });

    // 6. Rank resources using the staffing algorithm
    const budgetLcrCentsPerHour =
      demand.budgetCents > 0 ? demand.budgetCents : undefined;

    const ranked = rankResources({
      requiredSkills,
      resources: enrichedResources,
      budgetLcrCentsPerHour,
    } as unknown as Parameters<typeof rankResources>[0]);

    // Value-score tiebreaker (same logic as staffing router)
    ranked.sort((a, b) => {
      if (Math.abs(a.score - b.score) <= 2) {
        const aVal = enrichedResources.find((r) => r.id === a.resourceId)?.valueScore ?? 0;
        const bVal = enrichedResources.find((r) => r.id === b.resourceId)?.valueScore ?? 0;
        return bVal - aVal;
      }
      return 0;
    });

    const topSuggestions = ranked.slice(0, TOP_N);
    if (topSuggestions.length === 0) return;

    // 7. Build notification message
    const suggestionSummary = topSuggestions
      .map((s) => `${s.resourceName} (${s.score}%)`)
      .join(", ");

    const title = `Staffing suggestions for ${roleName} on ${project.name}`;
    const body = `${topSuggestions.length} matching resources found for ${roleName} on ${project.name}: ${suggestionSummary}`;

    // 8. Notify all managers and admins
    const managers = await db.user.findMany({
      where: { systemRole: { in: ["ADMIN", "MANAGER"] } },
      select: { id: true },
    });

    await createNotificationsForUsers({
      db,
      userIds: managers.map((m) => m.id),
      type: "AUTO_STAFFING_SUGGESTION",
      category: "NOTIFICATION",
      priority: "NORMAL",
      title,
      body,
      entityId: demandRequirementId,
      entityType: "demand",
      link: `/staffing?demandId=${demandRequirementId}`,
      channel: "in_app",
    });
  } catch {
    // Fire-and-forget: swallow all errors to avoid disrupting the caller.
  }
}
