import { Injectable, Logger } from '@nestjs/common';
import { WagonStatus } from '@edr/types';
import { DataSource } from 'typeorm';

import { Booking } from '../modules/bookings/entities/booking.entity';
import { Company } from '../modules/companies/entities/company.entity';
import { Locomotive } from '../modules/locomotives/entities/locomotive.entity';
import { CargoType } from '../modules/rule-engine/entities/cargo-type.entity';
import { ServiceType } from '../modules/rule-engine/entities/service-type.entity';
import { Yard } from '../modules/rule-engine/entities/yard.entity';
import { ImportDjiboutiOperation } from '../modules/train-scheduling/entities/import-djibouti-operation.entity';
import { WagonAllocationContainerItem } from '../modules/train-schedules/entities/wagon-allocation-container-item.entity';
import { WagonBookingAllocation } from '../modules/train-schedules/entities/wagon-booking-allocation.entity';
import { TrainSchedule } from '../modules/train-schedules/entities/train-schedule.entity';
import { TrainScheduleBooking } from '../modules/train-schedules/entities/train-schedule-booking.entity';
import { TrainSetWagon } from '../modules/train-sets/entities/train-set-wagon.entity';
import { TrainSet } from '../modules/train-sets/entities/train-set.entity';
import { WagonType } from '../modules/wagon-types/entities/wagon-type.entity';
import { Wagon } from '../modules/wagons/entities/wagon.entity';
import { WarehouseInventory } from '../modules/warehouses/entities/warehouse-inventory.entity';
import { WarehouseYard } from '../modules/warehouses/entities/warehouse-yard.entity';
import { WarehouseZone } from '../modules/warehouses/entities/warehouse-zone.entity';
import { Warehouse } from '../modules/warehouses/entities/warehouse.entity';

type DemoDirection = 'IMPORT' | 'EXPORT';

interface DemoTrain {
  trainNumber: string;
  direction: DemoDirection;
  status: 'SCHEDULED' | 'DISPATCHED' | 'ARRIVED';
  bookingPrefix: string;
  departureOffsetHours: number;
}

const DEMO_TRAINS: DemoTrain[] = [
  {
    trainNumber: 'MSH-DEMO-IMP-01',
    direction: 'IMPORT',
    status: 'SCHEDULED',
    bookingPrefix: 'MSH-IMP-01',
    departureOffsetHours: 6,
  },
  {
    trainNumber: 'MSH-DEMO-IMP-02',
    direction: 'IMPORT',
    status: 'DISPATCHED',
    bookingPrefix: 'MSH-IMP-02',
    departureOffsetHours: -3,
  },
  {
    trainNumber: 'MSH-DEMO-IMP-03',
    direction: 'IMPORT',
    status: 'ARRIVED',
    bookingPrefix: 'MSH-IMP-03',
    departureOffsetHours: -14,
  },
  {
    trainNumber: 'MSH-DEMO-EXP-01',
    direction: 'EXPORT',
    status: 'SCHEDULED',
    bookingPrefix: 'MSH-EXP-01',
    departureOffsetHours: 8,
  },
  {
    trainNumber: 'MSH-DEMO-EXP-02',
    direction: 'EXPORT',
    status: 'DISPATCHED',
    bookingPrefix: 'MSH-EXP-02',
    departureOffsetHours: -2,
  },
  {
    trainNumber: 'MSH-DEMO-EXP-03',
    direction: 'EXPORT',
    status: 'ARRIVED',
    bookingPrefix: 'MSH-EXP-03',
    departureOffsetHours: -12,
  },
];

@Injectable()
export class MarshallingDemoTrainsSeeder {
  private readonly logger = new Logger(MarshallingDemoTrainsSeeder.name);

  constructor(private readonly dataSource: DataSource) {}

  async run(): Promise<void> {
    try {
      const yardRepo = this.dataSource.getRepository(Yard);
      const serviceTypeRepo = this.dataSource.getRepository(ServiceType);
      const cargoTypeRepo = this.dataSource.getRepository(CargoType);
      const wagonTypeRepo = this.dataSource.getRepository(WagonType);
      const warehouseRepo = this.dataSource.getRepository(Warehouse);
      const warehouseYardRepo = this.dataSource.getRepository(WarehouseYard);
      const warehouseZoneRepo = this.dataSource.getRepository(WarehouseZone);

      const djiboutiYard =
        (await yardRepo.findOne({ where: { code: 'NAGAD' } })) ??
        (await yardRepo.findOne({ where: { code: 'DJIB_PORT' } })) ??
        (await yardRepo.findOne({ where: { country: 'Djibouti' } }));
      const ethiopiaYard =
        (await yardRepo.findOne({ where: { code: 'INDODE' } })) ??
        (await yardRepo.findOne({ where: { code: 'MOJO' } })) ??
        (await yardRepo.findOne({ where: { country: 'Ethiopia' } }));
      const serviceType =
        (await serviceTypeRepo.findOne({ where: { code: 'RAIL_CONTAINER' } })) ??
        (await serviceTypeRepo.findOne({ where: { isActive: true } }));
      const cargoType = await cargoTypeRepo.findOne({ where: { isActive: true } });
      const wagonType =
        (await wagonTypeRepo.findOne({ where: { code: 'NW5' } })) ??
        (await wagonTypeRepo.findOne({ where: { supportsContainer: true } })) ??
        (await wagonTypeRepo.findOne({ where: { isActive: true } }));
      const warehouse = await warehouseRepo.findOne({ where: { code: 'INDODE_OPEN' } });
      const warehouseYard = warehouse
        ? await warehouseYardRepo.findOne({ where: { warehouseId: warehouse.id } })
        : null;
      const warehouseZone = warehouseYard
        ? await warehouseZoneRepo.findOne({ where: { yardId: warehouseYard.id } })
        : null;

      // bookings.company_id is NOT NULL — reuse any seeded company for the demo.
      const company = await this.dataSource
        .getRepository(Company)
        .findOne({ where: {}, order: { createdAt: 'ASC' } });

      const missing = [
        !djiboutiYard ? 'Djibouti yard' : '',
        !ethiopiaYard ? 'Ethiopia yard' : '',
        !serviceType ? 'service type' : '',
        !wagonType ? 'wagon type' : '',
        !warehouse ? 'INDODE_OPEN warehouse' : '',
        !warehouseYard ? 'warehouse yard' : '',
        !warehouseZone ? 'warehouse zone' : '',
        !company ? 'company' : '',
      ].filter(Boolean);
      if (missing.length) {
        this.logger.warn(`Cannot seed marshalling demo trains, missing: ${missing.join(', ')}`);
        return;
      }

      let seeded = 0;
      for (const demo of DEMO_TRAINS) {
        const created = await this.seedTrain(demo, {
          djiboutiYard: djiboutiYard!,
          ethiopiaYard: ethiopiaYard!,
          serviceType: serviceType!,
          cargoType,
          wagonType: wagonType!,
          warehouse: warehouse!,
          warehouseYard: warehouseYard!,
          warehouseZone: warehouseZone!,
          company: company!,
        });
        if (created) seeded += 1;
      }

      this.logger.log(`Marshalling demo trains ready: ${seeded} new train(s) seeded, 6 total expected`);
    } catch (error) {
      this.logger.error(
        `MarshallingDemoTrainsSeeder failed: ${error instanceof Error ? error.message : String(error)}`,
      );
    }
  }

  private async seedTrain(
    demo: DemoTrain,
    refs: {
      djiboutiYard: Yard;
      ethiopiaYard: Yard;
      serviceType: ServiceType;
      cargoType: CargoType | null;
      wagonType: WagonType;
      warehouse: Warehouse;
      warehouseYard: WarehouseYard;
      warehouseZone: WarehouseZone;
      company: Company;
    },
  ): Promise<boolean> {
    const bookingRepo = this.dataSource.getRepository(Booking);
    const trainSetRepo = this.dataSource.getRepository(TrainSet);
    const trainSetWagonRepo = this.dataSource.getRepository(TrainSetWagon);
    const scheduleRepo = this.dataSource.getRepository(TrainSchedule);
    const scheduleBookingRepo = this.dataSource.getRepository(TrainScheduleBooking);
    const allocationRepo = this.dataSource.getRepository(WagonBookingAllocation);
    const containerItemRepo = this.dataSource.getRepository(WagonAllocationContainerItem);

    const existing = await scheduleRepo.findOne({ where: { trainNumber: demo.trainNumber } });
    if (existing) {
      await this.backfillDispatchQueueInventory(demo, refs);
      return false;
    }

    const now = new Date();
    const departure = this.addHours(now, demo.departureOffsetHours);
    const arrival = this.addHours(departure, demo.direction === 'IMPORT' ? 12 : 10);
    const isDispatched = demo.status === 'DISPATCHED';
    const isArrived = demo.status === 'ARRIVED';
    const hasDeparted = isDispatched || isArrived;
    const originYard = demo.direction === 'IMPORT' ? refs.djiboutiYard : refs.ethiopiaYard;
    const destinationYard = demo.direction === 'IMPORT' ? refs.ethiopiaYard : refs.djiboutiYard;

    const locomotive = await this.ensureLocomotive(originYard.id);
    const bookingWeights = [22.4, 24.8, 18.6, 20.2];
    const totalWeight = bookingWeights.reduce((sum, weight) => sum + weight, 0);
    const wagonCapacity = Number(refs.wagonType.capacityTons) || 70;
    const wagonLength = Number(refs.wagonType.lengthMeters) || 14;

    const trainSet = await trainSetRepo.save(
      trainSetRepo.create({
        locomotiveId: locomotive.id,
        totalWeightTons: totalWeight,
        totalLengthMeters: wagonLength * bookingWeights.length,
        wagonCount: bookingWeights.length,
        status: isArrived ? 'COMPLETED' : isDispatched ? 'DISPATCHED' : 'ASSIGNED',
      }),
    );

    const schedule = await scheduleRepo.save(
      scheduleRepo.create({
        trainSetId: trainSet.id,
        originStationId: originYard.id,
        destinationStationId: destinationYard.id,
        scheduledDepartureDate: departure,
        scheduledArrivalDate: arrival,
        actualDepartureAt: hasDeparted ? departure : null,
        actualArrivalAt: isArrived ? arrival : null,
        status: demo.status as TrainSchedule['status'],
        trainNumber: demo.trainNumber,
        direction: demo.direction,
        maxWagons: 53,
        bookingWindowStatus: 'CLOSED',
      }),
    );

    for (const [index, weightTons] of bookingWeights.entries()) {
      const sequence = index + 1;
      const bookingReference = `${demo.bookingPrefix}-${String(sequence).padStart(3, '0')}`;
      const containerNumber = `${demo.direction === 'IMPORT' ? 'IMDU' : 'EXPU'}${demo.trainNumber.slice(-2)}${String(sequence).padStart(3, '0')}`;

      const booking = await bookingRepo.save(
        bookingRepo.create({
          reference: bookingReference,
          companyId: refs.company.id,
          originYardId: originYard.id,
          destinationYardId: destinationYard.id,
          serviceTypeId: refs.serviceType.id,
          status: hasDeparted ? 'IN_TRANSIT' : 'PAID',
          paymentStatus: 'PAID',
          scheduledDate: departure,
          contractType: 'SPOT',
          equipmentReturn: 'TERMINAL',
          paymentCurrency: 'ETB',
          totalAmount: 0,
          isGovernment: false,
          tradeDirection: demo.direction,
          freightType: sequence % 2 === 0 ? 'BULK' : 'CONTAINER',
          cargoTypeId: refs.cargoType?.id ?? null,
          cargoFreeText: refs.cargoType ? null : `${demo.direction} marshalling demo goods ${sequence}`,
          cargoTotalWeightVgm: weightTons * 1000,
          trainScheduleId: schedule.id,
          schedulingStatus: isArrived ? 'ARRIVED' : isDispatched ? 'DISPATCHED' : 'SCHEDULED',
          scheduledAt: now,
        }),
      );
      await this.ensureDispatchQueueInventory({
        booking,
        demo,
        refs,
        weightKg: weightTons * 1000,
        now,
      });

      const physicalWagon = await this.ensureWagon({
        wagonNumber: `${demo.trainNumber}-W${String(sequence).padStart(2, '0')}`,
        wagonTypeId: refs.wagonType.id,
        yardId: originYard.id,
        trainScheduleId: schedule.id,
        dispatched: hasDeparted,
      });

      const trainSetWagon = await trainSetWagonRepo.save(
        trainSetWagonRepo.create({
          trainSetId: trainSet.id,
          wagonTypeId: refs.wagonType.id,
          physicalWagonId: physicalWagon.id,
          sequenceNo: sequence,
          capacityTons: wagonCapacity,
          lengthMeters: wagonLength,
          assignedWeightTons: weightTons,
          status: hasDeparted ? 'DEPARTED' : 'LOADED',
        }),
      );

      await this.dataSource.getRepository(Wagon).update(physicalWagon.id, {
        trainSetWagonId: trainSetWagon.id,
      });

      const allocation = await allocationRepo.save(
        allocationRepo.create({
          trainSetWagonId: trainSetWagon.id,
          bookingId: booking.id,
          allocatedWeightTons: weightTons,
          loadType: booking.freightType === 'CONTAINER' ? 'CONTAINER' : 'BULK',
          status: hasDeparted ? 'DEPARTED' : 'LOADED',
          confirmedAt: now,
        }),
      );

      await containerItemRepo.save(
        containerItemRepo.create({
          wagonBookingAllocationId: allocation.id,
          containerNumber,
          positionOnWagon: 1,
          sealNumber: `SEAL-${demo.trainNumber.slice(-2)}-${sequence}`,
          chassisNumber: `CHS-${demo.trainNumber.slice(-2)}-${sequence}`,
          grossWeightTons: weightTons,
        }),
      );

      await scheduleBookingRepo.save(
        scheduleBookingRepo.create({
          trainScheduleId: schedule.id,
          bookingId: booking.id,
        }),
      );
    }

    if (demo.direction === 'IMPORT') {
      await this.seedImportOperation(schedule.id, demo.trainNumber, now, departure, hasDeparted);
    }
    return true;
  }

  private async backfillDispatchQueueInventory(
    demo: DemoTrain,
    refs: {
      warehouse: Warehouse;
      warehouseYard: WarehouseYard;
      warehouseZone: WarehouseZone;
    },
  ): Promise<void> {
    const bookingRepo = this.dataSource.getRepository(Booking);
    for (let sequence = 1; sequence <= 4; sequence++) {
      const reference = `${demo.bookingPrefix}-${String(sequence).padStart(3, '0')}`;
      const booking = await bookingRepo.findOne({ where: { reference } });
      if (!booking) continue;
      await this.ensureDispatchQueueInventory({
        booking,
        demo,
        refs,
        weightKg: Number(booking.cargoTotalWeightVgm) || 0,
        now: new Date(),
      });
    }
  }

  private async ensureDispatchQueueInventory(input: {
    booking: Booking;
    demo: DemoTrain;
    refs: {
      warehouse: Warehouse;
      warehouseYard: WarehouseYard;
      warehouseZone: WarehouseZone;
    };
    weightKg: number;
    now: Date;
  }): Promise<void> {
    const repo = this.dataSource.getRepository(WarehouseInventory);
    const existing = await repo.findOne({ where: { bookingId: input.booking.id } });
    if (existing) return;

    const exportDispatch = input.demo.direction === 'EXPORT';
    const arrivedAt = this.addHours(input.now, -8);
    const inspectedAt = this.addHours(input.now, -6);
    const readyAt = this.addHours(input.now, -4);
    const loadedAt = this.addHours(input.now, -2);

    await repo.save(
      repo.create({
        warehouseId: input.refs.warehouse.id,
        yardId: input.refs.warehouseYard.id,
        zoneId: input.refs.warehouseZone.id,
        bookingId: input.booking.id,
        quantity: 1,
        weight: input.weightKg,
        status: exportDispatch ? 'LOADED' : 'READY_FOR_PICKUP',
        inspectionStatus: 'PASSED',
        arrivedAt,
        unloadedAt: exportDispatch ? null : arrivedAt,
        inspectedAt,
        readyForLoadingAt: exportDispatch ? readyAt : null,
        loadedAt: exportDispatch ? loadedAt : null,
        readyForPickupAt: exportDispatch ? null : readyAt,
        notes: `[MSH-DEMO] ${input.demo.trainNumber} dispatch queue test item`,
      }),
    );
  }

  private async ensureLocomotive(currentYardId: string): Promise<Locomotive> {
    const repo = this.dataSource.getRepository(Locomotive);
    const existing = await repo.findOne({ where: { code: 'MSH-DEMO-LOCO' } });
    if (existing) return existing;
    return repo.save(
      repo.create({
        code: 'MSH-DEMO-LOCO',
        name: 'Marshalling Demo Locomotive',
        locomotiveType: 'DIESEL',
        maxPullWeightTons: 4200,
        maxTrainLengthMeters: 760,
        status: 'AVAILABLE',
        currentYardId,
      }),
    );
  }

  private async ensureWagon(input: {
    wagonNumber: string;
    wagonTypeId: string;
    yardId: string;
    trainScheduleId: string;
    dispatched: boolean;
  }): Promise<Wagon> {
    const repo = this.dataSource.getRepository(Wagon);
    const existing = await repo.findOne({ where: { wagonNumber: input.wagonNumber } });
    if (existing) return existing;
    return repo.save(
      repo.create({
        wagonNumber: input.wagonNumber,
        wagonTypeId: input.wagonTypeId,
        currentYardId: input.yardId,
        currentTrainScheduleId: input.trainScheduleId,
        status: WagonStatus.Assigned,
        notes: 'Marshalling demo seed wagon',
      }),
    );
  }

  private async seedImportOperation(
    trainScheduleId: string,
    trainNumber: string,
    now: Date,
    departure: Date,
    dispatched: boolean,
  ): Promise<void> {
    const repo = this.dataSource.getRepository(ImportDjiboutiOperation);
    await repo.save(
      repo.create({
        trainScheduleId,
        documents: {
          DELIVERY_ORDER: this.documentRecord(trainNumber, 'DELIVERY_ORDER', now),
          PORT_INVOICE: this.documentRecord(trainNumber, 'PORT_INVOICE', now),
          DJIBOUTI_T1: this.documentRecord(trainNumber, 'DJIBOUTI_T1', now),
          ETHIOPIA_T1: this.documentRecord(trainNumber, 'ETHIOPIA_T1', now),
          RAILWAY_BILL: this.documentRecord(trainNumber, 'RAILWAY_BILL', now),
        },
        gatepassGrantedAt: now,
        readyForLoadingAt: now,
        loadedOnTrainAt: now,
        departedFromDjiboutiAt: dispatched ? departure : null,
        performedBy: 'Marshalling Demo Seeder',
        notes: '[MSH-DEMO] Import train ready for marshalling document and dispatch workflow',
      }),
    );
  }

  private documentRecord(trainNumber: string, type: string, now: Date) {
    return {
      reference: `${type}-${trainNumber}`,
      uploadedAt: now.toISOString(),
      uploadedBy: 'Marshalling Demo Seeder',
      notes: 'Seeded document for import marshalling workflow',
    };
  }

  private addHours(date: Date, hours: number): Date {
    return new Date(date.getTime() + hours * 60 * 60 * 1000);
  }
}
