import { AllocationStatus } from "@capakraken/shared";
import { z } from "zod";
import { findUniqueOrThrow } from "../db/helpers.js";
import { anonymizeResource, getAnonymizationDirectory } from "../lib/anonymization.js";
import { planningReadProcedure } from "../trpc.js";
import { buildResourceAvailabilitySummary, buildResourceAvailabilityView } from "./allocation-availability.js";
import { ASSIGNMENT_INCLUDE, DEMAND_INCLUDE } from "./allocation-shared.js";
import { getDemandRequirementByIdOrThrow, loadAllocationReadModel, resolveAssignmentBySelection } from "./allocation-support.js";

export const allocationReadProcedures = {
  list: planningReadProcedure
    .input(
      z.object({
        projectId: z.string().optional(),
        resourceId: z.string().optional(),
        status: z.nativeEnum(AllocationStatus).optional(),
      }),
    )
    .query(async ({ ctx, input }) => {
      const readModel = await loadAllocationReadModel(ctx.db, input);
      return readModel.allocations;
    }),

  listView: planningReadProcedure
    .input(
      z.object({
        projectId: z.string().optional(),
        resourceId: z.string().optional(),
        status: z.nativeEnum(AllocationStatus).optional(),
      }),
    )
    .query(async ({ ctx, input }) => loadAllocationReadModel(ctx.db, input)),

  listDemands: planningReadProcedure
    .input(
      z.object({
        projectId: z.string().optional(),
        status: z.nativeEnum(AllocationStatus).optional(),
        roleId: z.string().optional(),
      }),
    )
    .query(async ({ ctx, input }) => {
      const demands = await ctx.db.demandRequirement.findMany({
        where: {
          ...(input.projectId ? { projectId: input.projectId } : {}),
          ...(input.status ? { status: input.status } : {}),
          ...(input.roleId ? { roleId: input.roleId } : {}),
        },
        include: DEMAND_INCLUDE,
        orderBy: { startDate: "asc" },
      });
      const directory = await getAnonymizationDirectory(ctx.db);
      if (!directory) {
        return demands;
      }
      return demands.map((demand) => ({
        ...demand,
        assignments: demand.assignments.map((assignment) => (
          assignment.resource
            ? { ...assignment, resource: anonymizeResource(assignment.resource, directory) }
            : assignment
        )),
      }));
    }),

  listAssignments: planningReadProcedure
    .input(
      z.object({
        projectId: z.string().optional(),
        resourceId: z.string().optional(),
        status: z.nativeEnum(AllocationStatus).optional(),
        demandRequirementId: z.string().optional(),
      }),
    )
    .query(async ({ ctx, input }) => {
      const assignments = await ctx.db.assignment.findMany({
        where: {
          ...(input.projectId ? { projectId: input.projectId } : {}),
          ...(input.resourceId ? { resourceId: input.resourceId } : {}),
          ...(input.status ? { status: input.status } : {}),
          ...(input.demandRequirementId ? { demandRequirementId: input.demandRequirementId } : {}),
        },
        include: ASSIGNMENT_INCLUDE,
        orderBy: { startDate: "asc" },
      });
      const directory = await getAnonymizationDirectory(ctx.db);
      if (!directory) {
        return assignments;
      }
      return assignments.map((assignment) => (
        assignment.resource
          ? { ...assignment, resource: anonymizeResource(assignment.resource, directory) }
          : assignment
      ));
    }),

  getAssignmentById: planningReadProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      const assignment = await findUniqueOrThrow(
        ctx.db.assignment.findUnique({
          where: { id: input.id },
          include: ASSIGNMENT_INCLUDE,
        }),
        "Assignment",
      );
      const directory = await getAnonymizationDirectory(ctx.db);
      if (!directory || !assignment.resource) {
        return assignment;
      }
      return {
        ...assignment,
        resource: anonymizeResource(assignment.resource, directory),
      };
    }),

  resolveAssignment: planningReadProcedure
    .input(z.object({
      assignmentId: z.string().optional(),
      resourceId: z.string().optional(),
      projectId: z.string().optional(),
      startDate: z.coerce.date().optional(),
      endDate: z.coerce.date().optional(),
      selectionMode: z.enum(["WINDOW", "EXACT_START"]).default("EXACT_START"),
      excludeCancelled: z.boolean().default(false),
    }))
    .query(async ({ ctx, input }) => resolveAssignmentBySelection(ctx.db, input)),

  getDemandRequirementById: planningReadProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => getDemandRequirementByIdOrThrow(ctx.db, input.id)),

  checkResourceAvailability: planningReadProcedure
    .input(z.object({
      resourceId: z.string(),
      startDate: z.coerce.date(),
      endDate: z.coerce.date(),
      hoursPerDay: z.number().min(0.5).max(24).default(8),
    }))
    .query(async ({ ctx, input }) => {
      const { vacations: _vacations, ...availability } = await buildResourceAvailabilityView(ctx.db, input);
      return availability;
    }),

  getResourceAvailabilityView: planningReadProcedure
    .input(z.object({
      resourceId: z.string(),
      startDate: z.coerce.date(),
      endDate: z.coerce.date(),
      hoursPerDay: z.number().min(0.5).max(24).default(8),
    }))
    .query(async ({ ctx, input }) => buildResourceAvailabilityView(ctx.db, input)),

  getResourceAvailabilitySummary: planningReadProcedure
    .input(z.object({
      resourceId: z.string(),
      startDate: z.coerce.date(),
      endDate: z.coerce.date(),
      hoursPerDay: z.number().min(0.5).max(24).default(8),
    }))
    .query(async ({ ctx, input }) => {
      const availability = await buildResourceAvailabilityView(ctx.db, input);
      return buildResourceAvailabilitySummary(availability, input);
    }),
};
