import {
  updateAssignment,
  updateDemandRequirement,
  type SplitAssignmentRecord,
  type SplitDemandRequirementRecord,
} from "@capakraken/application";
import type { PrismaClient } from "@capakraken/db";
import { validateShift } from "@capakraken/engine";
import type { ShiftValidationResult } from "@capakraken/shared";
import { TRPCError } from "@trpc/server";
import type { TimelineShiftPlan } from "./timeline-shift-planning.js";
import {
  buildTimelineProjectDateRangeUpdate,
  buildTimelineProjectShiftAuditChanges,
  buildTimelineShiftedAssignmentUpdate,
  recalculateShiftedAssignmentDailyCost,
} from "./timeline-shift-mutation-support.js";

export interface TimelineShiftProjectRecord {
  id: string;
  budgetCents: number;
  winProbability: number;
  startDate: Date;
  endDate: Date;
}

export interface LoadedTimelineShiftContext {
  project: TimelineShiftProjectRecord;
  demandRequirements: SplitDemandRequirementRecord[];
  assignments: SplitAssignmentRecord[];
  shiftPlan: TimelineShiftPlan;
}

export interface ApplyTimelineProjectShiftInput {
  db: PrismaClient;
  projectId: string;
  newStartDate: Date;
  newEndDate: Date;
  context: LoadedTimelineShiftContext;
}

export interface TimelineProjectShiftEventPayload extends Record<string, unknown> {
  projectId: string;
  newStartDate: string;
  newEndDate: string;
  costDeltaCents: number;
  resourceIds: Array<string | null>;
}

export function buildTimelineProjectShiftValidation(input: {
  context: LoadedTimelineShiftContext;
  newStartDate: Date;
  newEndDate: Date;
}) {
  const { context, newStartDate, newEndDate } = input;

  return validateShift({
    project: {
      id: context.project.id,
      budgetCents: context.project.budgetCents,
      winProbability: context.project.winProbability,
      startDate: context.project.startDate,
      endDate: context.project.endDate,
    },
    newStartDate,
    newEndDate,
    allocations: context.shiftPlan.validationAllocations,
  });
}

export function assertTimelineProjectShiftValid(
  validation: ShiftValidationResult,
): void {
  if (validation.valid) {
    return;
  }

  throw new TRPCError({
    code: "BAD_REQUEST",
    message: `Shift validation failed: ${validation.errors.map((error) => error.message).join(", ")}`,
  });
}

export function buildTimelineProjectShiftEventPayload(input: {
  projectId: string;
  newStartDate: Date;
  newEndDate: Date;
  validation: ShiftValidationResult;
  assignments: SplitAssignmentRecord[];
}): TimelineProjectShiftEventPayload {
  return {
    projectId: input.projectId,
    newStartDate: input.newStartDate.toISOString(),
    newEndDate: input.newEndDate.toISOString(),
    costDeltaCents: input.validation.costImpact.deltaCents,
    resourceIds: input.assignments.map((assignment) => assignment.resourceId),
  };
}

export async function applyTimelineProjectShift(input: ApplyTimelineProjectShiftInput) {
  const validation = buildTimelineProjectShiftValidation({
    context: input.context,
    newStartDate: input.newStartDate,
    newEndDate: input.newEndDate,
  });
  assertTimelineProjectShiftValid(validation);

  const updatedProject = await input.db.$transaction(async (tx) => {
    const projectRecord = await tx.project.update({
      where: { id: input.projectId },
      data: buildTimelineProjectDateRangeUpdate(input),
    });

    for (const demandRequirement of input.context.demandRequirements) {
      await updateDemandRequirement(
        tx as unknown as Parameters<typeof updateDemandRequirement>[0],
        demandRequirement.id,
        buildTimelineProjectDateRangeUpdate(input),
      );
    }

    for (const assignment of input.context.assignments) {
      const dailyCostCents = await recalculateShiftedAssignmentDailyCost({
        db: input.db,
        assignment,
        newStartDate: input.newStartDate,
        newEndDate: input.newEndDate,
      });

      await updateAssignment(
        tx as unknown as Parameters<typeof updateAssignment>[0],
        assignment.id,
        buildTimelineShiftedAssignmentUpdate({
          newStartDate: input.newStartDate,
          newEndDate: input.newEndDate,
          dailyCostCents,
        }),
      );
    }

    await tx.auditLog.create({
      data: {
        entityType: "Project",
        entityId: input.projectId,
        action: "SHIFT",
        changes: buildTimelineProjectShiftAuditChanges({
          project: input.context.project,
          newStartDate: input.newStartDate,
          newEndDate: input.newEndDate,
          validation,
        }),
      },
    });

    return projectRecord;
  });

  return {
    project: updatedProject,
    validation,
    event: buildTimelineProjectShiftEventPayload({
      projectId: input.projectId,
      newStartDate: input.newStartDate,
      newEndDate: input.newEndDate,
      validation,
      assignments: input.context.assignments,
    }),
  };
}
