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

import { deriveTradeDirection } from '../../common/derive-trade-direction.util';

/**
 * READ-ONLY view into the train-scheduling / wagons domain for the warehouse module.
 *
 * IMPORTANT: this facade only ever runs SELECTs. The warehouse must never modify
 * wagon assignment, rescheduling, import_ready/export_ready, or locomotive flow.
 * It is intentionally decoupled (raw SQL) so it does not import the scheduling
 * services/entities and cannot accidentally write to them.
 */
export interface ImportTrainRow {
  scheduleId: string;
  trainNumber: string | null;
  route: string | null;
  origin: string | null;
  destination: string | null;
  arrivalTime: string | null;
  totalBookings: number;
  totalContainers: number;
  totalCargoes: number;
  unloadedBookings: number;
  pendingUnloadBookings: number;
  fullyUnloaded: boolean;
  status: string;
}

export interface ImportTrainItemRow {
  bookingId: string;
  bookingReference: string | null;
  customerId: string | null;
  customerName: string | null;
  wagonNumber: string | null;
  sequenceNo: number | null;
  allocatedWeightTons: number | null;
  freightType: string | null;
  containerNumber: string | null;
  cargoType: string | null;
  weight: number | null;
  arrivalTime: string | null;
  currentStatus: string | null;
  inspectionStatus: string | null;
  lastMileRequested: boolean;
  pickupOption: string;
}

export interface ExportDjiboutiQueueFilter {
  scheduleId?: string;
  destination?: string;
  status?: string;
  dateFrom?: string;
  dateTo?: string;
}

export interface ExportTrainRow extends ImportTrainRow {
  departureTime: string | null;
}

export interface ExportTrainItemRow {
  bookingId: string;
  bookingReference: string | null;
  customerId: string | null;
  customerName: string | null;
  wagonNumber: string | null;
  sequenceNo: number | null;
  allocatedWeightTons: number | null;
  itemType: 'CONTAINER' | 'CARGO';
  itemId: string | null;
  inventoryId: string | null;
  containerNumber: string | null;
  cargoType: string | null;
  weight: number | null;
  origin: string | null;
  destination: string | null;
  trainSchedule: string | null;
  arrivalTime: string | null;
  currentStatus: string | null;
}
export interface WagonView {
  id: string;
  wagonNumber: string;
  status: string;
  trainId: string | null;
}

export interface BookingScheduleView {
  schedule: {
    id: string;
    status: string;
    scheduledDepartureDate: string | null;
    scheduledArrivalDate: string | null;
    originStationId: string | null;
    destinationStationId: string | null;
  } | null;
  wagon: {
    wagonId: string | null;
    wagonNumber: string | null;
    sequenceNo: number | null;
    allocatedWeightTons: number | null;
  } | null;
  /** Mirror of schedule.status — the headline "where is the train" indicator. */
  departureStatus: string | null;
}

@Injectable()
export class SchedulingReadFacade {
  constructor(private readonly dataSource: DataSource) {}

  private isDjiboutiPortDestination(value: string | null | undefined): boolean {
    const normalized = (value ?? '').toUpperCase();
    return ['DJIBOUTI', 'DORALEH', 'DMP', 'DCT', 'NAGAD'].some((token) =>
      normalized.includes(token),
    );
  }

  /** Look up a single physical wagon. Returns null if it does not exist. */
  async findWagon(wagonId: string): Promise<WagonView | null> {
    const rows = await this.dataSource.query(
      `SELECT id, wagon_number AS "wagonNumber", status, train_id AS "trainId"
       FROM freight.wagons
       WHERE id = $1 AND deleted_at IS NULL
       LIMIT 1`,
      [wagonId],
    );
    return rows?.[0] ?? null;
  }

  /** True when the wagon is already part of a train set (selected by an existing schedule). */
  async isWagonScheduled(wagonId: string): Promise<boolean> {
    const rows = await this.dataSource.query(
      `SELECT 1 FROM freight.train_set_wagons
       WHERE physical_wagon_id = $1 AND deleted_at IS NULL
       LIMIT 1`,
      [wagonId],
    );
    return (rows?.length ?? 0) > 0;
  }

  /** List wagons usable for loading (available, or already assigned to a schedule). */
  listLoadableWagons(): Promise<WagonView[]> {
    return this.dataSource.query(
      `SELECT id, wagon_number AS "wagonNumber", status, train_id AS "trainId"
       FROM freight.wagons
       WHERE deleted_at IS NULL
         AND UPPER(status) NOT IN ('DETAINED', 'MAINTENANCE')
       ORDER BY wagon_number ASC`,
    );
  }

  /**
   * Given a booking, return its related schedule, wagon assignment and departure status.
   * All fields are read straight from the scheduling tables — nothing is written.
   */
  async getBookingSchedule(bookingId: string): Promise<BookingScheduleView> {
    const scheduleRows = await this.dataSource.query(
      `SELECT ts.id,
              ts.status,
              ts.scheduled_departure_date AS "scheduledDepartureDate",
              ts.scheduled_arrival_date   AS "scheduledArrivalDate",
              ts.origin_station_id        AS "originStationId",
              ts.destination_station_id   AS "destinationStationId"
       FROM freight.train_schedule_bookings tsb
       INNER JOIN freight.train_schedules ts ON ts.id = tsb.train_schedule_id
       WHERE tsb.booking_id = $1 AND ts.deleted_at IS NULL
       ORDER BY ts.scheduled_departure_date DESC NULLS LAST
       LIMIT 1`,
      [bookingId],
    );
    const schedule = scheduleRows?.[0] ?? null;

    const wagonRows = await this.dataSource.query(
      `SELECT w.id          AS "wagonId",
              w.wagon_number AS "wagonNumber",
              tsw.sequence_no AS "sequenceNo",
              wba.allocated_weight_tons AS "allocatedWeightTons"
       FROM freight.wagon_booking_allocations wba
       INNER JOIN freight.train_set_wagons tsw ON tsw.id = wba.train_set_wagon_id
       LEFT JOIN freight.wagons w ON w.id = tsw.physical_wagon_id
       WHERE wba.booking_id = $1
       ORDER BY tsw.sequence_no ASC NULLS LAST
       LIMIT 1`,
      [bookingId],
    );
    const wagon = wagonRows?.[0] ?? null;

    return {
      schedule,
      wagon,
      departureStatus: schedule?.status ?? null,
    };
  }

  /**
   * ARRIVED train schedules whose route is IMPORT (origin country = Djibouti), with per-train
   * booking/container/cargo counts. Direction is derived from the origin/destination station
   * countries (route-based), so EXPORT/DOMESTIC trains never appear. Read-only.
   */
  async importArriveQueue(): Promise<ImportTrainRow[]> {
    const rows: Array<
      ImportTrainRow & { originCountry: string | null; destinationCountry: string | null }
    > = await this.dataSource.query(
      `SELECT ts.id                  AS "scheduleId",
              ts.train_number         AS "trainNumber",
              oy.code                 AS "origin",
              dy.code                 AS "destination",
              oy.country              AS "originCountry",
              dy.country              AS "destinationCountry",
              COALESCE(ts.actual_arrival_at, ts.scheduled_arrival_date) AS "arrivalTime",
              ts.status,
              (SELECT count(*) FROM freight.train_schedule_bookings tsb
                 WHERE tsb.train_schedule_id = ts.id AND tsb.deleted_at IS NULL) AS "totalBookings",
              (SELECT count(*) FROM freight.containers c
                 JOIN freight.train_schedule_bookings tsbc ON tsbc.booking_id = c.booking_id AND tsbc.deleted_at IS NULL
                WHERE tsbc.train_schedule_id = ts.id AND c.deleted_at IS NULL) AS "totalContainers",
              (SELECT count(*) FROM freight.cargoes cg
                 JOIN freight.train_schedule_bookings tsbg ON tsbg.booking_id = cg.booking_id AND tsbg.deleted_at IS NULL
                WHERE tsbg.train_schedule_id = ts.id AND cg.deleted_at IS NULL) AS "totalCargoes",
              (SELECT count(*) FROM freight.train_schedule_bookings tsbp
                 JOIN freight.bookings bp ON bp.id = tsbp.booking_id AND bp.deleted_at IS NULL
                WHERE tsbp.train_schedule_id = ts.id
                  AND tsbp.deleted_at IS NULL
                  AND bp.status NOT IN ('DRAFT', 'REJECTED', 'CANCELLED', 'COMPLETED')
                  AND (
                    NOT EXISTS (
                      SELECT 1 FROM freight.warehouse_inventory invp
                       WHERE invp.booking_id = bp.id AND invp.deleted_at IS NULL
                    )
                    OR EXISTS (
                      SELECT 1 FROM freight.warehouse_inventory invr
                       WHERE invr.booking_id = bp.id
                         AND invr.deleted_at IS NULL
                         AND invr.status = 'RECEIVED'
                    )
                  )) AS "pendingUnloadBookings"
         FROM freight.train_schedules ts
         LEFT JOIN freight.yards oy ON oy.id = ts.origin_station_id
         LEFT JOIN freight.yards dy ON dy.id = ts.destination_station_id
        WHERE ts.deleted_at IS NULL
          AND ts.status = 'ARRIVED'
        ORDER BY COALESCE(ts.actual_arrival_at, ts.scheduled_arrival_date) DESC NULLS LAST`,
    );

    return rows
      .filter(
        (r) =>
          deriveTradeDirection({ country: r.originCountry }, { country: r.destinationCountry }) === 'IMPORT',
      )
      .map(({ originCountry: _oc, destinationCountry: _dc, ...rest }) => {
        const totalBookings = Number(rest.totalBookings) || 0;
        const pendingUnloadBookings = Number(rest.pendingUnloadBookings) || 0;
        const unloadedBookings = Math.max(totalBookings - pendingUnloadBookings, 0);

        return {
          ...rest,
          totalBookings,
          totalContainers: Number(rest.totalContainers) || 0,
          totalCargoes: Number(rest.totalCargoes) || 0,
          unloadedBookings,
          pendingUnloadBookings,
          fullyUnloaded: totalBookings > 0 && pendingUnloadBookings === 0,
          route: rest.origin || rest.destination ? `${rest.origin ?? '?'} → ${rest.destination ?? '?'}` : null,
        };
      });
  }

  /** Assigned bookings/items for an arrived import train (one row per booking). Read-only. */
  async importTrainDetail(scheduleId: string): Promise<ImportTrainItemRow[]> {
    const rows: ImportTrainItemRow[] = await this.dataSource.query(
      `SELECT b.id                    AS "bookingId",
              b.reference             AS "bookingReference",
              b.company_id            AS "customerId",
              company.name            AS "customerName",
              w.wagon_number          AS "wagonNumber",
              tsw.sequence_no          AS "sequenceNo",
              wba.allocated_weight_tons AS "allocatedWeightTons",
              b.freight_type           AS "freightType",
              (SELECT c.container_number FROM freight.containers c
                WHERE c.booking_id = b.id AND c.deleted_at IS NULL
                ORDER BY c.container_number LIMIT 1) AS "containerNumber",
              COALESCE(cgt.cargo_type_name, b.cargo_free_text) AS "cargoType",
              b.cargo_total_weight_vgm AS "weight",
              COALESCE(ts.actual_arrival_at, ts.scheduled_arrival_date) AS "arrivalTime",
              COALESCE(inv.status, b.status) AS "currentStatus",
              inv.inspection_status AS "inspectionStatus",
              (b.last_mile_delivery_address IS NOT NULL) AS "lastMileRequested",
              CASE WHEN b.last_mile_delivery_address IS NOT NULL
                   THEN 'DOOR_DELIVERY' ELSE 'TERMINAL_PICKUP' END AS "pickupOption"
         FROM freight.train_schedule_bookings tsb
         JOIN freight.train_schedules ts ON ts.id = tsb.train_schedule_id
         JOIN freight.bookings b ON b.id = tsb.booking_id AND b.deleted_at IS NULL
         LEFT JOIN freight.wagon_booking_allocations wba ON wba.booking_id = b.id
           AND wba.deleted_at IS NULL
           AND EXISTS (
             SELECT 1
               FROM freight.train_set_wagons tsw_match
              WHERE tsw_match.id = wba.train_set_wagon_id
                AND tsw_match.train_set_id = ts.train_set_id
                AND tsw_match.deleted_at IS NULL
           )
         LEFT JOIN freight.train_set_wagons tsw ON tsw.id = wba.train_set_wagon_id
           AND tsw.train_set_id = ts.train_set_id
           AND tsw.deleted_at IS NULL
         LEFT JOIN freight.wagons w ON w.id = tsw.physical_wagon_id AND w.deleted_at IS NULL
         LEFT JOIN freight.companies company ON company.id = b.company_id
         LEFT JOIN freight.cargo_types cgt ON cgt.id = b.cargo_type_id
         LEFT JOIN freight.warehouse_inventory inv ON inv.booking_id = b.id AND inv.deleted_at IS NULL
        WHERE tsb.train_schedule_id = $1 AND tsb.deleted_at IS NULL
        ORDER BY tsw.sequence_no ASC NULLS LAST, b.reference ASC NULLS LAST`,
      [scheduleId],
    );
    return rows;
  }

  /**
   * ARRIVED export train schedules at Djibouti-side destinations, with assigned item counts.
   * Read-only: this only selects from scheduling/booking/inventory tables.
   */
  async exportDjiboutiArrivalQueue(
    filter: ExportDjiboutiQueueFilter = {},
  ): Promise<ExportTrainRow[]> {
    const params: unknown[] = [];
    const where = [
      'ts.deleted_at IS NULL',
      "ts.status = ANY($1)",
      `EXISTS (
        SELECT 1
          FROM freight.train_schedule_bookings tsb_exists
          JOIN freight.bookings b_exists ON b_exists.id = tsb_exists.booking_id AND b_exists.deleted_at IS NULL
          LEFT JOIN freight.warehouse_inventory inv_exists ON inv_exists.booking_id = b_exists.id AND inv_exists.deleted_at IS NULL
         WHERE tsb_exists.train_schedule_id = ts.id
           AND tsb_exists.deleted_at IS NULL
           AND (inv_exists.status = ANY($2) OR b_exists.status = ANY($2))
      )`,
    ];
    params.push(
      filter.status ? [filter.status] : ['ARRIVED', 'ARRIVED_AT_DJIBOUTI'],
      [
        'LOADED',
        'DISPATCHED',
        'IN_TRANSIT',
        'ARRIVED',
        'ARRIVED_AT_DJIBOUTI',
        'ARRIVED_AT_PORT',
        'ARRIVED_AT_DESTINATION',
        'UNLOADED_AT_DJIBOUTI_PORT',
      ],
    );

    if (filter.scheduleId) {
      params.push(filter.scheduleId);
      where.push(`ts.id = $${params.length}`);
    }
    if (filter.destination) {
      params.push(`%${filter.destination}%`);
      where.push(`(dy.code ILIKE $${params.length} OR dy.label ILIKE $${params.length})`);
    }
    if (filter.dateFrom) {
      params.push(filter.dateFrom);
      where.push(`COALESCE(ts.actual_arrival_at, ts.scheduled_arrival_date) >= $${params.length}`);
    }
    if (filter.dateTo) {
      params.push(filter.dateTo);
      where.push(`COALESCE(ts.actual_arrival_at, ts.scheduled_arrival_date) <= $${params.length}`);
    }

    const rows: Array<
      ExportTrainRow & {
        originCountry: string | null;
        destinationCountry: string | null;
        destinationName: string | null;
      }
    > = await this.dataSource.query(
      `SELECT ts.id                  AS "scheduleId",
              ts.train_number         AS "trainNumber",
              oy.code                 AS "origin",
              dy.code                 AS "destination",
              dy.label                AS "destinationName",
              oy.country              AS "originCountry",
              dy.country              AS "destinationCountry",
              ts.scheduled_departure_date AS "departureTime",
              COALESCE(ts.actual_arrival_at, ts.scheduled_arrival_date) AS "arrivalTime",
              ts.status,
              (SELECT count(*) FROM freight.train_schedule_bookings tsb
                 WHERE tsb.train_schedule_id = ts.id AND tsb.deleted_at IS NULL) AS "totalBookings",
              (SELECT count(*) FROM freight.containers c
                 JOIN freight.train_schedule_bookings tsbc ON tsbc.booking_id = c.booking_id AND tsbc.deleted_at IS NULL
                WHERE tsbc.train_schedule_id = ts.id AND c.deleted_at IS NULL) AS "totalContainers",
              (SELECT count(*) FROM freight.cargoes cg
                 JOIN freight.train_schedule_bookings tsbg ON tsbg.booking_id = cg.booking_id AND tsbg.deleted_at IS NULL
                WHERE tsbg.train_schedule_id = ts.id AND cg.deleted_at IS NULL) AS "totalCargoes"
         FROM freight.train_schedules ts
         LEFT JOIN freight.yards oy ON oy.id = ts.origin_station_id
         LEFT JOIN freight.yards dy ON dy.id = ts.destination_station_id
        WHERE ${where.join(' AND ')}
        ORDER BY COALESCE(ts.actual_arrival_at, ts.scheduled_arrival_date) DESC NULLS LAST`,
      params,
    );

    return rows
      .filter((r) => {
        const direction = deriveTradeDirection(
          { country: r.originCountry },
          { country: r.destinationCountry },
        );
        return (
          direction === 'EXPORT' &&
          this.isDjiboutiPortDestination(`${r.destination ?? ''} ${r.destinationName ?? ''}`)
        );
      })
      .map(({ originCountry: _oc, destinationCountry: _dc, destinationName: _dn, ...rest }) => ({
        ...rest,
        totalBookings: Number(rest.totalBookings) || 0,
        totalContainers: Number(rest.totalContainers) || 0,
        totalCargoes: Number(rest.totalCargoes) || 0,
        route: rest.origin || rest.destination ? `${rest.origin ?? '?'} -> ${rest.destination ?? '?'}` : null,
      }));
  }

  /** Assigned export booking items for an arrived Djibouti-side export train. Read-only. */
  async exportDjiboutiTrainDetail(scheduleId: string): Promise<ExportTrainItemRow[]> {
    const rows: ExportTrainItemRow[] = await this.dataSource.query(
      `WITH assigned AS (
          SELECT b.id AS booking_id,
                 b.reference,
                 b.company_id,
                 company.name AS customer_name,
                 w.wagon_number,
                 tsw.sequence_no,
                 wba.allocated_weight_tons,
                 COALESCE(cgt.cargo_type_name, b.cargo_free_text) AS cargo_type,
                 b.cargo_total_weight_vgm AS booking_weight,
                 oy.code AS origin,
                 dy.code AS destination,
                 ts.train_number,
                 COALESCE(ts.actual_arrival_at, ts.scheduled_arrival_date) AS arrival_time,
                 inv.id AS inventory_id,
                 COALESCE(inv.status, b.status) AS current_status
            FROM freight.train_schedule_bookings tsb
            JOIN freight.train_schedules ts ON ts.id = tsb.train_schedule_id
            JOIN freight.bookings b ON b.id = tsb.booking_id AND b.deleted_at IS NULL
            LEFT JOIN freight.wagon_booking_allocations wba ON wba.booking_id = b.id
              AND wba.deleted_at IS NULL
              AND EXISTS (
                SELECT 1
                  FROM freight.train_set_wagons tsw_match
                 WHERE tsw_match.id = wba.train_set_wagon_id
                   AND tsw_match.train_set_id = ts.train_set_id
                   AND tsw_match.deleted_at IS NULL
              )
            LEFT JOIN freight.train_set_wagons tsw ON tsw.id = wba.train_set_wagon_id
              AND tsw.train_set_id = ts.train_set_id
              AND tsw.deleted_at IS NULL
            LEFT JOIN freight.wagons w ON w.id = tsw.physical_wagon_id AND w.deleted_at IS NULL
            LEFT JOIN freight.companies company ON company.id = b.company_id
            LEFT JOIN freight.cargo_types cgt ON cgt.id = b.cargo_type_id
            LEFT JOIN freight.yards oy ON oy.id = b.origin_yard_id
            LEFT JOIN freight.yards dy ON dy.id = b.destination_yard_id
            LEFT JOIN freight.warehouse_inventory inv ON inv.booking_id = b.id AND inv.deleted_at IS NULL
           WHERE tsb.train_schedule_id = $1 AND tsb.deleted_at IS NULL
        )
        SELECT a.booking_id AS "bookingId",
               a.reference AS "bookingReference",
               a.company_id AS "customerId",
               a.customer_name AS "customerName",
               a.wagon_number AS "wagonNumber",
               a.sequence_no AS "sequenceNo",
               a.allocated_weight_tons AS "allocatedWeightTons",
               'CONTAINER' AS "itemType",
               c.id AS "itemId",
               a.inventory_id AS "inventoryId",
               c.container_number AS "containerNumber",
               a.cargo_type AS "cargoType",
               a.booking_weight AS "weight",
               a.origin,
               a.destination,
               a.train_number AS "trainSchedule",
               a.arrival_time AS "arrivalTime",
               a.current_status AS "currentStatus"
          FROM assigned a
          JOIN freight.containers c ON c.booking_id = a.booking_id AND c.deleted_at IS NULL
        UNION ALL
        SELECT a.booking_id AS "bookingId",
               a.reference AS "bookingReference",
               a.company_id AS "customerId",
               a.customer_name AS "customerName",
               a.wagon_number AS "wagonNumber",
               a.sequence_no AS "sequenceNo",
               a.allocated_weight_tons AS "allocatedWeightTons",
               'CARGO' AS "itemType",
               cg.id AS "itemId",
               a.inventory_id AS "inventoryId",
               NULL AS "containerNumber",
               COALESCE(cgt.cargo_type_name, a.cargo_type) AS "cargoType",
               COALESCE(cg.weight, a.booking_weight) AS "weight",
               a.origin,
               a.destination,
               a.train_number AS "trainSchedule",
               a.arrival_time AS "arrivalTime",
               a.current_status AS "currentStatus"
          FROM assigned a
          JOIN freight.cargoes cg ON cg.booking_id = a.booking_id AND cg.deleted_at IS NULL
          LEFT JOIN freight.cargo_types cgt ON cgt.id = cg.cargo_type_id
        UNION ALL
        SELECT a.booking_id AS "bookingId",
               a.reference AS "bookingReference",
               a.company_id AS "customerId",
               a.customer_name AS "customerName",
               a.wagon_number AS "wagonNumber",
               a.sequence_no AS "sequenceNo",
               a.allocated_weight_tons AS "allocatedWeightTons",
               CASE WHEN a.cargo_type ILIKE '%container%' THEN 'CONTAINER' ELSE 'CARGO' END AS "itemType",
               a.inventory_id AS "itemId",
               a.inventory_id AS "inventoryId",
               NULL AS "containerNumber",
               a.cargo_type AS "cargoType",
               a.booking_weight AS "weight",
               a.origin,
               a.destination,
               a.train_number AS "trainSchedule",
               a.arrival_time AS "arrivalTime",
               a.current_status AS "currentStatus"
          FROM assigned a
         WHERE NOT EXISTS (SELECT 1 FROM freight.containers c WHERE c.booking_id = a.booking_id AND c.deleted_at IS NULL)
           AND NOT EXISTS (SELECT 1 FROM freight.cargoes cg WHERE cg.booking_id = a.booking_id AND cg.deleted_at IS NULL)
         ORDER BY "sequenceNo" ASC NULLS LAST, "bookingReference" ASC NULLS LAST, "itemType" ASC`,
      [scheduleId],
    );
    return rows;
  }
}
