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

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

export const resourceMarketplaceReadProcedures = {
  getSkillMarketplace: controllerProcedure
    .input(
      z.object({
        searchSkill: z.string().optional(),
        minProficiency: z.number().int().min(1).max(5).optional().default(1),
        availableOnly: z.boolean().optional().default(false),
      }),
    )
    .query(async ({ ctx, input }) => {
      const now = new Date();
      const today = new Date(now);
      today.setUTCHours(0, 0, 0, 0);
      const thirtyDaysFromNow = new Date(today);
      thirtyDaysFromNow.setUTCDate(thirtyDaysFromNow.getUTCDate() + 29);

      const resources = await ctx.db.resource.findMany({
        where: { isActive: true },
        select: {
          id: true,
          displayName: true,
          eid: true,
          chapter: true,
          skills: true,
          availability: true,
          countryId: true,
          federalState: true,
          metroCityId: true,
          country: { select: { code: true } },
          metroCity: { select: { name: true } },
        },
      });

      const assignments = await ctx.db.assignment.findMany({
        where: {
          resourceId: { in: resources.map((resource) => resource.id) },
          status: { in: ["PROPOSED", "CONFIRMED", "ACTIVE", "COMPLETED"] },
          endDate: { gte: today },
          startDate: { lte: thirtyDaysFromNow },
        },
        select: {
          resourceId: true,
          startDate: true,
          endDate: true,
          hoursPerDay: true,
        },
      });
      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,
        })),
        today,
        thirtyDaysFromNow,
      );
      const assignmentsByResourceId = new Map<string, typeof assignments>();
      for (const assignment of assignments) {
        const items = assignmentsByResourceId.get(assignment.resourceId) ?? [];
        items.push(assignment);
        assignmentsByResourceId.set(assignment.resourceId, items);
      }

      const utilizationMap = new Map<string, { utilizationPercent: number; earliestAvailableDate: Date | null }>();
      for (const resource of resources) {
        const availability = resource.availability as unknown as WeekdayAvailability;
        const context = contexts.get(resource.id);
        const resourceAssignments = assignmentsByResourceId.get(resource.id) ?? [];
        const todayAvailableHours = calculateEffectiveAvailableHours({
          availability,
          periodStart: today,
          periodEnd: today,
          context,
        });
        const todayBookedHours = resourceAssignments.reduce(
          (sum, assignment) => sum + calculateEffectiveBookedHours({
            availability,
            startDate: assignment.startDate,
            endDate: assignment.endDate,
            hoursPerDay: assignment.hoursPerDay,
            periodStart: today,
            periodEnd: today,
            context,
          }),
          0,
        );
        const utilizationPercent = todayAvailableHours > 0
          ? Math.round((todayBookedHours / todayAvailableHours) * 100)
          : 0;
        const dailyBookedHours = buildDailyBookedHoursMap(
          resourceAssignments,
          availability,
          context,
          today,
          thirtyDaysFromNow,
        );

        let earliestAvailableDate: Date | null = null;
        const checkDate = new Date(today);
        for (let index = 0; index < 30; index++) {
          const dayAvailableHours = calculateEffectiveDayAvailability({
            availability,
            date: checkDate,
            context,
          });
          if (dayAvailableHours > 0) {
            const dayBookedHours = dailyBookedHours.get(toIsoDate(checkDate)) ?? 0;
            if (dayBookedHours < dayAvailableHours * 0.8) {
              earliestAvailableDate = new Date(checkDate);
              break;
            }
          }
          checkDate.setUTCDate(checkDate.getUTCDate() + 1);
        }

        utilizationMap.set(resource.id, { utilizationPercent, earliestAvailableDate });
      }

      let searchResults: Array<{
        id: string;
        displayName: string;
        chapter: string | null;
        skillProficiency: number;
        skillName: string;
        utilizationPercent: number;
        availableFrom: string | null;
      }> = [];

      if (input.searchSkill && input.searchSkill.trim().length > 0) {
        const needle = input.searchSkill.toLowerCase();
        for (const resource of resources) {
          const skills = (resource.skills as unknown as SkillRow[]) ?? [];
          const match = skills.find(
            (skill) => skill.skill.toLowerCase().includes(needle) && skill.proficiency >= input.minProficiency,
          );
          if (!match) continue;

          const utilization = utilizationMap.get(resource.id);
          if (input.availableOnly && !utilization?.earliestAvailableDate) continue;

          searchResults.push({
            id: resource.id,
            displayName: resource.displayName,
            chapter: resource.chapter,
            skillProficiency: match.proficiency,
            skillName: match.skill,
            utilizationPercent: utilization?.utilizationPercent ?? 0,
            availableFrom: utilization?.earliestAvailableDate?.toISOString() ?? null,
          });
        }
        searchResults.sort((left, right) => (
          right.skillProficiency - left.skillProficiency || left.utilizationPercent - right.utilizationPercent
        ));
      }

      const unfilled = await ctx.db.demandRequirement.findMany({
        where: {
          endDate: { gte: now },
          assignments: { none: {} },
        },
        select: {
          id: true,
          role: true,
          roleId: true,
          headcount: true,
          project: {
            select: { staffingReqs: true },
          },
        },
      });

      const demandSkillCounts = new Map<string, number>();
      for (const demand of unfilled) {
        const staffingReqs = (demand.project.staffingReqs as unknown as Array<{
          role?: string;
          roleId?: string;
          requiredSkills?: string[];
        }>) ?? [];
        const matchedReq = staffingReqs.find(
          (staffingReq) =>
            (demand.roleId && staffingReq.roleId === demand.roleId) ||
            (demand.role && staffingReq.role === demand.role),
        );

        if (matchedReq?.requiredSkills) {
          for (const skill of matchedReq.requiredSkills) {
            demandSkillCounts.set(skill, (demandSkillCounts.get(skill) ?? 0) + demand.headcount);
          }
        }
      }

      const supplySkillCounts = new Map<string, number>();
      for (const resource of resources) {
        const skills = (resource.skills as unknown as SkillRow[]) ?? [];
        for (const skill of skills) {
          if (skill.proficiency >= 3) {
            supplySkillCounts.set(skill.skill, (supplySkillCounts.get(skill.skill) ?? 0) + 1);
          }
        }
      }

      const gapData = Array.from(new Set([...demandSkillCounts.keys(), ...supplySkillCounts.keys()]))
        .map((skill) => {
          const supply = supplySkillCounts.get(skill) ?? 0;
          const demand = demandSkillCounts.get(skill) ?? 0;
          return { skill, supply, demand, gap: demand - supply };
        })
        .sort((left, right) => right.gap - left.gap);

      const distribution = Array.from(
        (() => {
          const skillMap = new Map<string, { skill: string; count: number; totalProficiency: number }>();
          for (const resource of resources) {
            const skills = (resource.skills as unknown as SkillRow[]) ?? [];
            for (const skill of skills) {
              const entry = skillMap.get(skill.skill);
              if (entry) {
                entry.count++;
                entry.totalProficiency += skill.proficiency;
              } else {
                skillMap.set(skill.skill, {
                  skill: skill.skill,
                  count: 1,
                  totalProficiency: skill.proficiency,
                });
              }
            }
          }
          return skillMap.values();
        })(),
      )
        .map((entry) => ({
          skill: entry.skill,
          count: entry.count,
          avgProficiency: Math.round((entry.totalProficiency / entry.count) * 10) / 10,
        }))
        .sort((left, right) => right.count - left.count)
        .slice(0, 20);

      const directory = await getAnonymizationDirectory(ctx.db);

      return {
        searchResults: anonymizeResources(searchResults, directory),
        gapData,
        distribution,
        totalResources: resources.length,
      };
    }),
};
