import type { PrismaClient } from "@capakraken/db";
import { computeValueScore } from "@capakraken/staffing";
import { VALUE_SCORE_WEIGHTS, type WeekdayAvailability } from "@capakraken/shared";
import { listAssignmentBookings } from "../allocation/list-assignment-bookings.js";
import {
  calculateEffectiveAllocationHours,
  calculateEffectiveAvailableHours,
  loadDailyAvailabilityContexts,
} from "../dashboard/holiday-capacity.js";

type ResourceValueScoreDbClient = Partial<Pick<PrismaClient, "systemSettings" | "holidayCalendar" | "vacation">> &
  Pick<PrismaClient, "assignment" | "resource" | "$transaction">;

export interface RecomputeResourceValueScoresInput {
  daysBack?: number;
}

export async function recomputeResourceValueScores(
  db: ResourceValueScoreDbClient,
  input: RecomputeResourceValueScoresInput = {},
) {
  if (
    typeof db.resource?.findMany !== "function" ||
    typeof db.resource?.update !== "function"
  ) {
    return { updated: 0 };
  }

  const daysBack = input.daysBack ?? 90;
  const [resources, settings] = await Promise.all([
    db.resource.findMany({
      where: { isActive: true },
      select: {
        id: true,
        skills: true,
        lcrCents: true,
        chargeabilityTarget: true,
        availability: true,
        countryId: true,
        federalState: true,
        metroCityId: true,
        country: { select: { code: true } },
        metroCity: { select: { name: true } },
      },
    }),
    db.systemSettings?.findUnique?.({ where: { id: "singleton" } }) ?? Promise.resolve(null),
  ]);

  if (resources.length === 0) {
    return { updated: 0 };
  }

  const periodStart = new Date(Date.now() - daysBack * 24 * 60 * 60 * 1000);
  const periodEnd = new Date();
  const bookings = await listAssignmentBookings(db, {
    startDate: periodStart,
    endDate: periodEnd,
    resourceIds: resources.map((resource) => resource.id),
  });
  const contexts = await loadDailyAvailabilityContexts(
    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,
    })),
    periodStart,
    periodEnd,
  );

  const defaultWeights = {
    skillDepth: VALUE_SCORE_WEIGHTS.SKILL_DEPTH,
    skillBreadth: VALUE_SCORE_WEIGHTS.SKILL_BREADTH,
    costEfficiency: VALUE_SCORE_WEIGHTS.COST_EFFICIENCY,
    chargeability: VALUE_SCORE_WEIGHTS.CHARGEABILITY,
    experience: VALUE_SCORE_WEIGHTS.EXPERIENCE,
  };
  const weights =
    (settings?.scoreWeights as unknown as typeof defaultWeights | null) ?? defaultWeights;
  const maxLcrCents = resources.reduce((max, resource) => Math.max(max, resource.lcrCents), 0);
  const now = new Date();

  type SkillRow = {
    category?: string;
    isMainSkill?: boolean;
    proficiency: number;
    skill: string;
    yearsExperience?: number;
  };

  const updates = resources.map((resource) => {
    const resourceBookings = bookings.filter((booking) => booking.resourceId === resource.id);
    const availability = resource.availability as unknown as WeekdayAvailability;
    const context = contexts.get(resource.id);
    const availableHours = calculateEffectiveAvailableHours({
      availability,
      periodStart,
      periodEnd,
      context,
    });
    const bookedHours = resourceBookings.reduce(
      (sum, booking) =>
        sum + calculateEffectiveAllocationHours({
          availability,
          startDate: booking.startDate,
          endDate: booking.endDate,
          hoursPerDay: booking.hoursPerDay,
          periodStart,
          periodEnd,
          context,
        }),
      0,
    );
    const currentChargeability =
      availableHours > 0 ? Math.min(100, (bookedHours / availableHours) * 100) : 0;
    const skills = (resource.skills as unknown as SkillRow[]) ?? [];

    const breakdown = computeValueScore(
      {
        skills: skills as unknown as import("@capakraken/shared").SkillEntry[],
        lcrCents: resource.lcrCents,
        chargeabilityTarget: resource.chargeabilityTarget,
        currentChargeability,
        maxLcrCents,
      },
      weights,
    );

    return db.resource.update({
      where: { id: resource.id },
      data: {
        valueScore: breakdown.total,
        valueScoreBreakdown:
          breakdown as unknown as import("@capakraken/db").Prisma.InputJsonValue,
        valueScoreUpdatedAt: now,
      },
    });
  });

  await db.$transaction(updates);

  return { updated: updates.length };
}
