import { Injectable } from '@nestjs/common';

import { ContainerTypesService } from '../rule-engine/services/container-types.service';
import { Booking } from './entities/booking.entity';

export interface ConsolidationSlot {
  containerTypeId: string;
  containerTypeCode: string;
  quantity: number;
  containersPerWagon: number;
  remainder: number;
  slotsNeeded: number;
}

export interface ConsolidationAttemptResult {
  booking: Booking;
  partner: Booking | null;
  paired: boolean;
  messages: string[];
}

/** Containers that fit on one wagon for a given container type (inverse of wagons_per_unit). */
export function containersPerWagon(wagonsPerUnit: number): number {
  const wpu = Number(wagonsPerUnit);
  if (!wpu || wpu <= 0) return 1;
  return Math.max(1, Math.round(1 / wpu));
}

export function wagonRemainder(quantity: number, perWagon: number): number {
  const r = quantity % perWagon;
  return r;
}

export function slotsNeededToFillWagon(quantity: number, perWagon: number): number {
  const remainder = wagonRemainder(quantity, perWagon);
  if (remainder === 0) return 0;
  return perWagon - remainder;
}

/** Two bookings' quantities for the same type complete whole wagon(s). */
export function quantitiesComplementWagon(
  q1: number,
  q2: number,
  perWagon: number,
): boolean {
  return (
    wagonRemainder(q1, perWagon) > 0 &&
    wagonRemainder(q2, perWagon) > 0 &&
    (q1 + q2) % perWagon === 0
  );
}

@Injectable()
export class ConsolidationService {
  constructor(private readonly containerTypesService: ContainerTypesService) {}

  async slotsFromContainerLines(
    lines: Array<{ containerTypeId: string; quantity: number }>,
  ): Promise<ConsolidationSlot[]> {
    // Aggregate by container type first: two lines of the same type on one
    // booking share the same wagons. Counting them separately would flag a
    // self-complete booking (e.g. 2× 20FT = exactly one wagon) as a partial
    // wagon and wrongly park it in PENDING_CONSOLIDATION.
    const quantityByType = new Map<string, number>();
    for (const line of lines) {
      if (!line.containerTypeId) continue;
      quantityByType.set(
        line.containerTypeId,
        (quantityByType.get(line.containerTypeId) ?? 0) + Number(line.quantity || 0),
      );
    }

    const slots: ConsolidationSlot[] = [];
    for (const [containerTypeId, quantity] of quantityByType) {
      const ct = await this.containerTypesService.findById(containerTypeId);
      const perWagon = containersPerWagon(Number(ct.wagonsPerUnit));
      const remainder = wagonRemainder(quantity, perWagon);
      if (remainder === 0) continue;
      slots.push({
        containerTypeId,
        containerTypeCode: ct.code,
        quantity,
        containersPerWagon: perWagon,
        remainder,
        slotsNeeded: perWagon - remainder,
      });
    }
    return slots;
  }

  async slotsFromBooking(booking: Booking): Promise<ConsolidationSlot[]> {
    const lines = (booking.bookingContainers ?? [])
      .filter((bc): bc is typeof bc & { containerTypeId: string } => bc.containerTypeId != null)
      .map((bc) => ({
        containerTypeId: bc.containerTypeId,
        quantity: bc.quantity,
      }));
    return this.slotsFromContainerLines(lines);
  }

  async needsConsolidation(
    lines: Array<{ containerTypeId: string; quantity: number }>,
  ): Promise<boolean> {
    const slots = await this.slotsFromContainerLines(lines);
    return slots.length > 0;
  }

  async needsConsolidationFromBooking(booking: Booking): Promise<boolean> {
    const slots = await this.slotsFromBooking(booking);
    return slots.length > 0;
  }

  describePending(_booking: Booking, slots: ConsolidationSlot[]): string {
    if (slots.length === 0) {
      return 'Booking does not require wagon consolidation.';
    }
    const parts = slots.map(
      (s) =>
        `${s.slotsNeeded} more × ${s.containerTypeCode} (${s.containersPerWagon} per wagon; you have ${s.quantity})`,
    );
    return (
      `No compatible partner found yet. Your booking is queued (PENDING_CONSOLIDATION). ` +
      `Waiting for: ${parts.join('; ')}. You will be notified when another customer fills the wagon.`
    );
  }

  describePaired(partnerReference: string, slots: ConsolidationSlot[]): string {
    const parts = slots.map(
      (s) =>
        `${s.containerTypeCode}: ${s.quantity} + partner fills wagon (${s.containersPerWagon} per wagon)`,
    );
    return (
      `Consolidation partner found (${partnerReference}). ` +
      `Shared wagon confirmed: ${parts.join('; ')}.`
    );
  }
}
