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;
  status: string;
}

export interface ImportTrainItemRow {
  bookingId: string;
  bookingReference: string | null;
  customerId: string | null;
  customerName: string | null;
  containerNumber: string | null;
  cargoType: string | null;
  weight: number | null;
  arrivalTime: string | null;
  currentStatus: string | null;
  lastMileRequested: boolean;
  pickupOption: string;
}
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) {}

  /** 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 status NOT IN ('RETIRED', '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"
         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 }) => ({
        ...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 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",
              (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",
              (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.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 b.reference ASC NULLS LAST`,
      [scheduleId],
    );
    return rows;
  }
}
