import type { Booking } from '../bookings/entities/booking.entity';
import type { WagonType } from '../wagon-types/entities/wagon-type.entity';
import {
  buildBulkWagonPlan,
  buildContainerWagonPlan,
  buildMixedWagonPlan,
  roundTons,
  type WagonPlanSlot,
} from './wagon-plan.util';

export type FleetAvailabilityRow = {
  wagonTypeId: string;
  wagonTypeCode: string;
  needed: number;
  available: number;
  shortfall: number;
};

export type DeferredBookingRow = {
  id: string;
  reference: string;
  reason: string;
};

export function sortBookingsForScheduling(bookings: Booking[]): Booking[] {
  return [...bookings].sort((a, b) => {
    const govDiff = Number(Boolean(b.isGovernment)) - Number(Boolean(a.isGovernment));
    if (govDiff !== 0) return govDiff;

    const priorityDiff = (b.priorityScore ?? 0) - (a.priorityScore ?? 0);
    if (priorityDiff !== 0) return priorityDiff;

    const aTime = a.scheduledDate ? new Date(a.scheduledDate).getTime() : 0;
    const bTime = b.scheduledDate ? new Date(b.scheduledDate).getTime() : 0;
    return aTime - bTime;
  });
}

export function wagonsRequiredForBooking(booking: Booking, bulkWagonCapacity?: number): number {
  if (booking.freightType === 'BULK') {
    const weight = Number(booking.cargoTotalWeightVgm ?? 0);
    const capacity = bulkWagonCapacity && bulkWagonCapacity > 0 ? bulkWagonCapacity : 1;
    return Math.max(1, Math.ceil(weight / capacity));
  }

  const lineSlots = (booking.bookingContainers ?? []).reduce(
    (sum, line) => sum + Number(line.wagonsRequired ?? 0),
    0,
  );
  return Math.max(1, lineSlots);
}

export function countSlotsByType(wagonPlan: WagonPlanSlot[]): Map<string, { code: string; count: number }> {
  const map = new Map<string, { code: string; count: number }>();
  for (const slot of wagonPlan) {
    const existing = map.get(slot.wagonTypeId) ?? { code: slot.wagonTypeCode, count: 0 };
    existing.count += 1;
    map.set(slot.wagonTypeId, existing);
  }
  return map;
}

export function computeFleetAvailability(
  demandPlan: WagonPlanSlot[],
  fleetByTypeId: Map<string, number>,
  fleetTypeCodes: Map<string, string>,
): FleetAvailabilityRow[] {
  const neededByType = countSlotsByType(demandPlan);
  const typeIds = new Set([...neededByType.keys(), ...fleetByTypeId.keys()]);

  return [...typeIds].map((wagonTypeId) => {
    const needed = neededByType.get(wagonTypeId)?.count ?? 0;
    const available = fleetByTypeId.get(wagonTypeId) ?? 0;
    return {
      wagonTypeId,
      wagonTypeCode:
        neededByType.get(wagonTypeId)?.code ??
        fleetTypeCodes.get(wagonTypeId) ??
        wagonTypeId,
      needed,
      available,
      shortfall: Math.max(0, needed - available),
    };
  }).filter((row) => row.needed > 0 || row.available > 0);
}

export function selectBookingsWithinFleetCap(
  bookings: Booking[],
  fleetByTypeId: Map<string, number>,
  resolveWagonTypeId: (booking: Booking) => string,
  bulkWagonCapacity?: number,
): { fitting: Booking[]; deferred: DeferredBookingRow[] } {
  const remaining = new Map(fleetByTypeId);
  const fitting: Booking[] = [];
  const deferred: DeferredBookingRow[] = [];

  for (const booking of sortBookingsForScheduling(bookings)) {
    const typeId = resolveWagonTypeId(booking);
    const needed = wagonsRequiredForBooking(booking, bulkWagonCapacity);
    const available = remaining.get(typeId) ?? 0;

    if (available >= needed) {
      remaining.set(typeId, available - needed);
      fitting.push(booking);
      continue;
    }

    deferred.push({
      id: booking.id,
      reference: booking.reference,
      reason:
        available > 0
          ? `Needs ${needed} wagons but only ${available} available for this type`
          : `No available wagons for required type (${needed} needed)`,
    });
  }

  return { fitting, deferred };
}

export function buildCappedWagonPlan(params: {
  bookings: Booking[];
  resolvedMode: 'CONTAINER' | 'BULK' | 'MIXED';
  containerWagonType: WagonType;
  bulkWagonType: WagonType;
}): WagonPlanSlot[] {
  const { bookings, resolvedMode, containerWagonType, bulkWagonType } = params;

  if (resolvedMode === 'MIXED') {
    const containerBookings = bookings.filter((b) => b.freightType === 'CONTAINER');
    const bulkBookings = bookings.filter((b) => b.freightType === 'BULK');
    return buildMixedWagonPlan(
      containerBookings,
      bulkBookings,
      containerWagonType,
      bulkWagonType,
    );
  }

  if (resolvedMode === 'BULK') {
    return buildBulkWagonPlan(bookings, bulkWagonType);
  }

  return buildContainerWagonPlan(bookings, containerWagonType);
}

export function summarizeFleetWarnings(
  fleetAvailability: FleetAvailabilityRow[],
  deferred: DeferredBookingRow[],
): string[] {
  const warnings: string[] = [];

  for (const row of fleetAvailability.filter((r) => r.shortfall > 0)) {
    warnings.push(
      `Fleet shortage: need ${row.needed} ${row.wagonTypeCode}, only ${row.available} available (short ${row.shortfall})`,
    );
  }

  if (deferred.length) {
    warnings.push(
      `${deferred.length} booking(s) deferred to next train due to insufficient fleet wagons`,
    );
  }

  return warnings;
}

export function totalAssignedWeight(bookings: Booking[]): number {
  return roundTons(bookings.reduce((sum, b) => sum + Number(b.cargoTotalWeightVgm ?? 0), 0));
}
