import type { Prisma, PrismaClient } from "@capakraken/db";
import type { AllocationWithDetails } from "@capakraken/shared";
import { TRPCError } from "@trpc/server";
import { buildSplitAllocationReadModel } from "./build-split-allocation-read-model.js";
import {
  ASSIGNMENT_RELATIONS_INCLUDE,
  type AssignmentWithRelations,
} from "./create-assignment.js";
import {
  DEMAND_REQUIREMENT_RELATIONS_INCLUDE,
  type DemandRequirementWithRelations,
} from "./create-demand-requirement.js";

type DbClient =
  | Pick<PrismaClient, "demandRequirement" | "assignment">
  | Pick<Prisma.TransactionClient, "demandRequirement" | "assignment">;

export type AllocationEntryResolution =
  | {
      kind: "demand";
      entry: AllocationWithDetails;
      demandRequirement: DemandRequirementWithRelations;
      projectId: string;
      resourceId: null;
    }
  | {
      kind: "assignment";
      entry: AllocationWithDetails;
      assignment: AssignmentWithRelations;
      projectId: string;
      resourceId: string;
    };

function toDemandAllocationEntry(
  demandRequirement: DemandRequirementWithRelations,
): AllocationWithDetails {
  return buildSplitAllocationReadModel({
    demandRequirements: [demandRequirement],
    assignments: [],
  }).allocations[0] as AllocationWithDetails;
}

function toAssignmentAllocationEntry(
  assignment: AssignmentWithRelations,
): AllocationWithDetails {
  return buildSplitAllocationReadModel({
    demandRequirements: [],
    assignments: [assignment],
  }).allocations[0] as AllocationWithDetails;
}

async function loadDemandRequirementById(
  db: DbClient,
  id: string,
): Promise<DemandRequirementWithRelations | null> {
  return db.demandRequirement.findUnique({
    where: { id },
    include: DEMAND_REQUIREMENT_RELATIONS_INCLUDE,
  });
}

async function loadAssignmentById(
  db: DbClient,
  id: string,
): Promise<AssignmentWithRelations | null> {
  return db.assignment.findUnique({
    where: { id },
    include: ASSIGNMENT_RELATIONS_INCLUDE,
  });
}

/**
 * Resolves an id to a demand requirement or assignment.
 */
export async function findAllocationEntry(
  db: DbClient,
  id: string,
): Promise<AllocationEntryResolution | null> {
  const [demandRequirement, assignment] = await Promise.all([
    loadDemandRequirementById(db, id),
    loadAssignmentById(db, id),
  ]);

  if (demandRequirement) {
    return {
      kind: "demand",
      entry: toDemandAllocationEntry(demandRequirement),
      demandRequirement,
      projectId: demandRequirement.projectId,
      resourceId: null,
    };
  }

  if (assignment) {
    return {
      kind: "assignment",
      entry: toAssignmentAllocationEntry(assignment),
      assignment,
      projectId: assignment.projectId,
      resourceId: assignment.resourceId,
    };
  }

  return null;
}

export async function loadAllocationEntry(
  db: DbClient,
  id: string,
): Promise<AllocationEntryResolution> {
  const resolved = await findAllocationEntry(db, id);
  if (!resolved) {
    throw new TRPCError({ code: "NOT_FOUND", message: "Allocation not found" });
  }

  return resolved;
}
