import { Injectable } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource, In } from 'typeorm';

import { TrainSchedulingGlobalRules } from '../train-scheduling/entities/train-scheduling-global-rules.entity';
import { Booking } from './entities/booking.entity';
import { BookingContainerUnit } from './entities/booking-container-unit.entity';
import {
  Container20ftUnit,
  PairingViolation,
  validate20ftWeightPairing,
} from './container-pairing.util';

/** Default 20ft pair weight-difference cap when no global rules row exists (matches the entity default). */
const DEFAULT_MAX_20FT_PAIR_DIFF_TONS = 10;

/**
 * Booking-time container validations that need the customer-entered per-unit
 * weights (`BookingContainerUnit`): the 20ft weight-pairing rule. Kept out of the
 * rule engine (which works on line totals) because pairing is per physical unit.
 */
@Injectable()
export class ContainerValidationService {
  constructor(@InjectDataSource() private readonly dataSource: DataSource) {}

  private async maxPairDiffTons(): Promise<number> {
    const row = await this.dataSource
      .getRepository(TrainSchedulingGlobalRules)
      .find({ order: { createdAt: 'ASC' }, take: 1 })
      .then((rows) => rows[0] ?? null)
      .catch(() => null);
    const v = row?.max20ftPairWeightDiffTons;
    const n = v == null ? NaN : Number(v);
    return Number.isFinite(n) ? n : DEFAULT_MAX_20FT_PAIR_DIFF_TONS;
  }

  /** Load every 20ft container UNIT weight for a booking (customer-entered VGM). */
  private async load20ftUnits(booking: Booking): Promise<Container20ftUnit[]> {
    const lines = (booking.bookingContainers ?? []).filter(
      (bc) => (bc.containerSize ?? '').includes('20'),
    );
    if (!lines.length) return [];

    const units = await this.dataSource
      .getRepository(BookingContainerUnit)
      .find({
        where: { bookingContainerId: In(lines.map((l) => l.id)) },
        order: { sortOrder: 'ASC' },
      });

    return units.map((u) => ({
      label: u.containerNumber || u.id.slice(0, 8),
      grossWeightTons: Number(u.vgmTons ?? 0),
    }));
  }

  /**
   * Validate the 20ft weight-pairing rule for a booking. Returns one message per
   * pair whose weight difference exceeds the cap; empty when all 20ft can be
   * balanced onto wagons (or there is nothing to pair). A lone odd 20ft is fine —
   * it flows to consolidation. Callers hard-block a non-empty result.
   */
  async validate20ftPairing(booking: Booking): Promise<PairingViolation[]> {
    // Only bookings whose 20ft lines actually carry per-unit weights can be
    // checked; contract-drawdown bookings do (units are required there).
    const containerLines = booking.bookingContainers ?? [];
    const has20ft = containerLines.some((bc) => (bc.containerSize ?? '').includes('20'));
    if (!has20ft) return [];

    const units = await this.load20ftUnits(booking);
    if (units.length < 2) return [];

    const maxDiff = await this.maxPairDiffTons();
    return validate20ftWeightPairing(units, maxDiff);
  }
}
