import { buildSplitAllocationReadModel, loadAllocationEntry } from "@capakraken/application";
import { AllocationStatus, CreateDemandRequirementSchema } from "@capakraken/shared";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { findUniqueOrThrow } from "../db/helpers.js";
import { anonymizeResource, getAnonymizationDirectory } from "../lib/anonymization.js";
import { ASSIGNMENT_INCLUDE, type AllocationEntryUpdateInput, type AllocationListFilters, type AssignmentResolutionInput, type CreateDemandDraftInput, DEMAND_INCLUDE, toIsoDate } from "./allocation-shared.js";

export function toDemandRequirementUpdateInput(input: AllocationEntryUpdateInput) {
  return {
    ...(input.projectId !== undefined ? { projectId: input.projectId } : {}),
    ...(input.startDate !== undefined ? { startDate: input.startDate } : {}),
    ...(input.endDate !== undefined ? { endDate: input.endDate } : {}),
    ...(input.hoursPerDay !== undefined ? { hoursPerDay: input.hoursPerDay } : {}),
    ...(input.percentage !== undefined ? { percentage: input.percentage } : {}),
    ...(input.role !== undefined ? { role: input.role } : {}),
    ...(input.roleId !== undefined ? { roleId: input.roleId } : {}),
    ...(input.headcount !== undefined ? { headcount: input.headcount } : {}),
    ...(input.budgetCents !== undefined ? { budgetCents: input.budgetCents } : {}),
    ...(input.status !== undefined ? { status: input.status } : {}),
    ...(input.metadata !== undefined ? { metadata: input.metadata } : {}),
  };
}

export function toAssignmentUpdateInput(input: AllocationEntryUpdateInput) {
  return {
    ...(input.resourceId !== undefined ? { resourceId: input.resourceId } : {}),
    ...(input.projectId !== undefined ? { projectId: input.projectId } : {}),
    ...(input.startDate !== undefined ? { startDate: input.startDate } : {}),
    ...(input.endDate !== undefined ? { endDate: input.endDate } : {}),
    ...(input.hoursPerDay !== undefined ? { hoursPerDay: input.hoursPerDay } : {}),
    ...(input.percentage !== undefined ? { percentage: input.percentage } : {}),
    ...(input.role !== undefined ? { role: input.role } : {}),
    ...(input.roleId !== undefined ? { roleId: input.roleId } : {}),
    ...(input.status !== undefined ? { status: input.status } : {}),
    ...(input.metadata !== undefined ? { metadata: input.metadata } : {}),
  };
}

export async function loadAllocationReadModel(
  db: Pick<import("@capakraken/db").PrismaClient, "demandRequirement" | "assignment" | "systemSettings" | "resource">,
  input: AllocationListFilters,
) {
  const [demandRequirements, assignments] = await Promise.all([
    input.resourceId
      ? Promise.resolve([])
      : db.demandRequirement.findMany({
          where: {
            ...(input.projectId ? { projectId: input.projectId } : {}),
            ...(input.status ? { status: input.status } : {}),
          },
          include: DEMAND_INCLUDE,
          orderBy: { startDate: "asc" },
        }),
    db.assignment.findMany({
      where: {
        ...(input.projectId ? { projectId: input.projectId } : {}),
        ...(input.resourceId ? { resourceId: input.resourceId } : {}),
        ...(input.status ? { status: input.status } : {}),
      },
      include: ASSIGNMENT_INCLUDE,
      orderBy: { startDate: "asc" },
    }),
  ]);

  const readModel = buildSplitAllocationReadModel({ demandRequirements, assignments });
  const directory = await getAnonymizationDirectory(db as import("@capakraken/db").PrismaClient);
  if (!directory) {
    return readModel;
  }

  function anonymizeAllocation<T extends { resource?: { id: string; eid?: string | null; displayName?: string | null; email?: string | null } | null }>(allocation: T): T {
    if (!allocation.resource) {
      return allocation;
    }
    return { ...allocation, resource: anonymizeResource(allocation.resource, directory) };
  }

  return {
    ...readModel,
    allocations: readModel.allocations.map(anonymizeAllocation),
    demands: readModel.demands.map(anonymizeAllocation),
    assignments: readModel.assignments.map(anonymizeAllocation),
  };
}

export async function findAllocationEntryOrNull(
  db: Pick<import("@capakraken/db").PrismaClient, "demandRequirement" | "assignment">,
  id: string,
) {
  try {
    return await loadAllocationEntry(db, id);
  } catch (error) {
    if (error instanceof TRPCError && error.code === "NOT_FOUND") {
      return null;
    }

    throw error;
  }
}

export function buildCreateDemandRequirementInput(input: CreateDemandDraftInput): z.infer<typeof CreateDemandRequirementSchema> {
  return {
    projectId: input.projectId,
    startDate: input.startDate,
    endDate: input.endDate,
    hoursPerDay: input.hoursPerDay,
    percentage: (input.hoursPerDay / 8) * 100,
    status: AllocationStatus.PROPOSED,
    headcount: input.headcount ?? 1,
    budgetCents: input.budgetCents ?? 0,
    metadata: input.metadata ?? {},
    ...(input.role ? { role: input.role } : {}),
    ...(input.roleId ? { roleId: input.roleId } : {}),
  };
}

export async function getDemandRequirementByIdOrThrow(
  db: Pick<import("@capakraken/db").PrismaClient, "demandRequirement">,
  id: string,
) {
  return findUniqueOrThrow(
    db.demandRequirement.findUnique({
      where: { id },
      include: DEMAND_INCLUDE,
    }),
    "Demand requirement",
  );
}

export async function resolveAssignmentBySelection(
  db: Pick<import("@capakraken/db").PrismaClient, "assignment">,
  input: AssignmentResolutionInput,
) {
  if (input.assignmentId) {
    return findUniqueOrThrow(
      db.assignment.findUnique({
        where: { id: input.assignmentId },
        include: ASSIGNMENT_INCLUDE,
      }),
      "Assignment",
    );
  }

  if (!input.resourceId || !input.projectId) {
    throw new TRPCError({
      code: "BAD_REQUEST",
      message: "resourceId and projectId are required when assignmentId is not provided",
    });
  }

  const assignments = await db.assignment.findMany({
    where: {
      resourceId: input.resourceId,
      projectId: input.projectId,
      ...(input.excludeCancelled ? { status: { not: AllocationStatus.CANCELLED } } : {}),
    },
    include: ASSIGNMENT_INCLUDE,
    orderBy: { startDate: "asc" },
  });

  const matchingAssignment = assignments
    .filter((assignment) => {
      if (input.selectionMode === "WINDOW") {
        return (!input.startDate || assignment.startDate >= input.startDate)
          && (!input.endDate || assignment.endDate <= input.endDate);
      }

      return !input.startDate || toIsoDate(assignment.startDate) === toIsoDate(input.startDate);
    })
    .sort((left, right) => right.startDate.getTime() - left.startDate.getTime())[0] ?? null;

  if (!matchingAssignment) {
    throw new TRPCError({ code: "NOT_FOUND", message: "Assignment not found" });
  }

  return matchingAssignment;
}
