import type {
  Allocation,
  ConflictDetail,
  CostImpact,
  Resource,
  ShiftValidationResult,
  ValidationError,
  ValidationWarning,
} from "@capakraken/shared";
import { calculateAllocation } from "../allocation/calculator.js";
import { validateAvailability } from "../allocation/availability-validator.js";
import { computeBudgetStatus } from "../budget/monitor.js";

export interface ShiftInput {
  project: {
    id: string;
    budgetCents: number;
    winProbability: number;
    startDate: Date;
    endDate: Date;
  };
  newStartDate: Date;
  newEndDate: Date;
  allocations: (Pick<
    Allocation,
    "id" | "resourceId" | "startDate" | "endDate" | "hoursPerDay" | "percentage" | "role" | "dailyCostCents" | "status"
  > & {
    resource: Pick<Resource, "id" | "displayName" | "lcrCents" | "availability">;
    allAllocationsForResource: Pick<Allocation, "id" | "startDate" | "endDate" | "hoursPerDay" | "status" | "projectId">[];
    /** Extracted from allocation metadata before calling validator */
    includeSaturday?: boolean;
  })[];
}

/**
 * Validates a proposed project timeline shift.
 *
 * Pure function — all data passed in, no DB access.
 * Returns a comprehensive validation result with cost impact and conflicts.
 */
export function validateShift(input: ShiftInput): ShiftValidationResult {
  const { project, newStartDate, newEndDate, allocations } = input;
  const errors: ValidationError[] = [];
  const warnings: ValidationWarning[] = [];
  const conflictDetails: ConflictDetail[] = [];

  const durationMs = project.endDate.getTime() - project.startDate.getTime();
  const newDurationMs = newEndDate.getTime() - newStartDate.getTime();

  // Validate date range
  if (newEndDate < newStartDate) {
    errors.push({
      code: "INVALID_DATE_RANGE",
      message: "New end date must be after new start date",
      field: "newEndDate",
    });
    return buildResult(false, errors, warnings, conflictDetails, buildZeroCostImpact(project.budgetCents));
  }

  // Warn if duration changed significantly
  const durationDeltaDays = Math.abs((newDurationMs - durationMs) / (1000 * 60 * 60 * 24));
  if (durationDeltaDays > 7) {
    warnings.push({
      code: "DURATION_CHANGED",
      message: `Project duration changed by ${Math.round(durationDeltaDays)} days`,
    });
  }

  // Calculate shifted allocation dates and check availability
  let newTotalCostCents = 0;
  let currentTotalCostCents = 0;

  for (const alloc of allocations) {
    const { resource, allAllocationsForResource } = alloc;
    const includeSaturday = alloc.includeSaturday ?? false;

    // Compute current allocation cost
    const currentCalc = calculateAllocation({
      lcrCents: resource.lcrCents,
      hoursPerDay: alloc.hoursPerDay,
      startDate: new Date(alloc.startDate),
      endDate: new Date(alloc.endDate),
      availability: resource.availability,
      includeSaturday,
    });
    currentTotalCostCents += currentCalc.totalCostCents;

    // Shift allocation proportionally within the new project window
    const shiftedStart = new Date(newStartDate);
    const shiftedEnd = new Date(newEndDate);

    // Validate availability for shifted period (excluding current project's allocations)
    const otherAllocations = allAllocationsForResource.filter(
      (a) => a.projectId !== project.id,
    );
    const availResult = validateAvailability(
      shiftedStart,
      shiftedEnd,
      alloc.hoursPerDay,
      resource.availability,
      otherAllocations,
      includeSaturday,
    );

    if (!availResult.valid) {
      const conflictDays = availResult.conflicts.map(
        (c) => c.date.toISOString().split("T")[0] ?? "",
      );
      conflictDetails.push({
        resourceId: resource.id,
        resourceName: resource.displayName,
        conflictType: "availability",
        days: conflictDays,
        message: `${resource.displayName} has availability conflicts on ${availResult.totalConflictDays} day(s)`,
      });

      if (availResult.totalConflictDays > 5) {
        errors.push({
          code: "AVAILABILITY_CONFLICT",
          message: `${resource.displayName}: ${availResult.totalConflictDays} day(s) exceed capacity`,
          resourceId: resource.id,
        });
      } else {
        warnings.push({
          code: "AVAILABILITY_WARNING",
          message: `${resource.displayName}: ${availResult.totalConflictDays} day(s) may have capacity issues`,
          resourceId: resource.id,
        });
      }
    }

    // Compute new cost for shifted period
    const newCalc = calculateAllocation({
      lcrCents: resource.lcrCents,
      hoursPerDay: alloc.hoursPerDay,
      startDate: shiftedStart,
      endDate: shiftedEnd,
      availability: resource.availability,
      includeSaturday,
    });
    newTotalCostCents += newCalc.totalCostCents;
  }

  // Budget impact check
  const currentStatus = computeBudgetStatus(
    project.budgetCents,
    project.winProbability,
    allocations.map((a) => ({
      status: a.status,
      dailyCostCents: a.dailyCostCents,
      startDate: a.startDate,
      endDate: a.endDate,
      hoursPerDay: a.hoursPerDay,
    })),
    project.startDate,
    project.endDate,
  );

  const wouldExceedBudget = newTotalCostCents > project.budgetCents;
  if (wouldExceedBudget) {
    errors.push({
      code: "BUDGET_EXCEEDED",
      message: `Shift would exceed budget by ${((newTotalCostCents - project.budgetCents) / 100).toFixed(2)} EUR`,
    });
  } else if (newTotalCostCents > project.budgetCents * 0.95) {
    warnings.push({
      code: "BUDGET_NEAR_LIMIT",
      message: `Shift would use ${((newTotalCostCents / project.budgetCents) * 100).toFixed(1)}% of budget`,
    });
  }

  const costImpact: CostImpact = {
    currentTotalCents: currentTotalCostCents,
    newTotalCents: newTotalCostCents,
    deltaCents: newTotalCostCents - currentTotalCostCents,
    budgetCents: project.budgetCents,
    budgetUtilizationBefore: currentStatus.utilizationPercent,
    budgetUtilizationAfter:
      project.budgetCents > 0 ? (newTotalCostCents / project.budgetCents) * 100 : 0,
    wouldExceedBudget,
  };

  return buildResult(errors.length === 0, errors, warnings, conflictDetails, costImpact);
}

function buildResult(
  valid: boolean,
  errors: ValidationError[],
  warnings: ValidationWarning[],
  conflictDetails: ConflictDetail[],
  costImpact: CostImpact,
): ShiftValidationResult {
  return { valid, errors, warnings, costImpact, conflictDetails };
}

function buildZeroCostImpact(budgetCents: number): CostImpact {
  return {
    currentTotalCents: 0,
    newTotalCents: 0,
    deltaCents: 0,
    budgetCents,
    budgetUtilizationBefore: 0,
    budgetUtilizationAfter: 0,
    wouldExceedBudget: false,
  };
}
