import { Injectable, Logger } from '@nestjs/common';
import { EntityManager } from 'typeorm';

import { generateGrnNumber } from '../../common/grn.util';
import { YardFacilitiesService } from '../rule-engine/services/yard-facilities.service';
import { Booking } from '../bookings/entities/booking.entity';
import {
  FacilityHandlingEvent,
  FacilityHandlingEventType,
} from './entities/facility-handling-event.entity';

/**
 * Records cargo being loaded/unloaded at a yard's facility, and raises its GRN.
 *
 * Every facility raises a GRN — the goods changed hands, whether or not anyone
 * stores them. What differs is what happens next: a facility with a warehouse
 * (Indode) keeps the cargo, so it goes through the normal warehouse flow and
 * accrues storage/demurrage; the rest only move it between train and truck, so
 * the event and its GRN are the whole record.
 *
 * Best-effort by design: a failure here must not undo a load/unload that
 * physically happened.
 */
@Injectable()
export class FacilityHandlingService {
  private readonly logger = new Logger(FacilityHandlingService.name);

  constructor(private readonly yardFacilities: YardFacilitiesService) {}

  /**
   * Write the handling event and mint its GRN. Returns the GRN, or null when the
   * yard has no facility (nothing to record) or the write failed.
   */
  async recordHandling(
    manager: EntityManager,
    input: {
      booking: Booking;
      yardId: string;
      trainScheduleId?: string | null;
      eventType: FacilityHandlingEventType;
      performedBy?: string | null;
      occurredAt?: Date;
    },
  ): Promise<string | null> {
    const { booking, yardId, eventType } = input;
    try {
      const facility = await this.yardFacilities.facilityForYard(yardId);
      if (!facility?.hasFacility) return null;

      const occurredAt = input.occurredAt ?? new Date();
      const grnNumber = generateGrnNumber(
        booking.tradeDirection ?? 'DOMESTIC',
        booking.id,
        occurredAt,
      );

      // Link the storage record when this facility keeps cargo — that link is
      // what ties an Indode handover to its storage/demurrage.
      let inventoryId: string | null = null;
      if (facility.hasWarehouse) {
        const [inv]: Array<{ id: string }> = await manager.query(
          `SELECT id FROM freight.warehouse_inventory
            WHERE booking_id = $1 AND deleted_at IS NULL
            ORDER BY created_at DESC LIMIT 1`,
          [booking.id],
        );
        inventoryId = inv?.id ?? null;
      }

      // The handed-over weight: the booking's declared VGM, else what its
      // containers actually carry. A GRN without a weight is not a receipt.
      let weightTons = Number(booking.cargoTotalWeightVgm) || null;
      if (!weightTons) {
        const [sum]: Array<{ tons: string | null }> = await manager.query(
          `SELECT SUM(bcu.vgm_tons) AS tons
             FROM freight.booking_container_units bcu
             JOIN freight.booking_container bc
               ON bc.id = bcu.booking_container_id AND bc.deleted_at IS NULL
            WHERE bc.booking_id = $1 AND bcu.deleted_at IS NULL`,
          [booking.id],
        );
        weightTons = Number(sum?.tons) || null;
      }

      const repo = manager.getRepository(FacilityHandlingEvent);
      await repo.save(
        repo.create({
          bookingId: booking.id,
          yardId,
          trainScheduleId: input.trainScheduleId ?? null,
          eventType,
          grnNumber,
          weightTons,
          inventoryId,
          performedBy: input.performedBy ?? null,
          occurredAt,
        }),
      );

      this.logger.log(
        `GRN ${grnNumber} raised on ${eventType} at ${facility.yardCode ?? yardId} for booking ${booking.reference ?? booking.id}`,
      );
      return grnNumber;
    } catch (err) {
      // The cargo moved regardless — never fail the journey over the paperwork.
      this.logger.error(
        `Facility ${eventType} record failed for booking ${booking.id} at yard ${yardId}: ${String(err)}`,
      );
      return null;
    }
  }
}
