import { Injectable, Logger } from "@nestjs/common";
import { randomUUID } from "crypto";
import { DataSource } from "typeorm";

import { BookingContainer } from "../modules/bookings/entities/booking-container.entity";
import { Booking } from "../modules/bookings/entities/booking.entity";
import {
  Company,
  CompanyStatus,
  CompanyType,
} from "../modules/companies/entities/company.entity";
import { Locomotive } from "../modules/locomotives/entities/locomotive.entity";
import { ServiceType } from "../modules/rule-engine/entities/service-type.entity";
import { Yard } from "../modules/rule-engine/entities/yard.entity";
import { WagonType } from "../modules/wagon-types/entities/wagon-type.entity";
import { CargoType } from "../modules/rule-engine/entities/cargo-type.entity";
import { ContainerType } from "../modules/rule-engine/entities/container-type.entity";
import { Container } from "../modules/container-management/entities/container.entity";
import { Route } from "../modules/routes/entities/route.entity";
import { RouteMilestone } from "../modules/routes/entities/route-milestone.entity";
import { Wagon } from "../modules/wagons/entities/wagon.entity";
import { WagonStatus } from "@edr/types";

const SEED_FLAG = "SEED_DEMO_BOOKINGS";

const SERVICE_TYPE_CODE = "RAIL_CONTAINER";
const COMPANY_EMAIL = "train-scheduling-demo@edr.local";
const COMPANY_TIN = "1234567890";

const YARDS = [
  { code: "DJIBOUTI", label: "Djibouti", country: "Djibouti", displayOrder: 1 },
  {
    code: "ADDIS_ABABA",
    label: "Addis Ababa",
    country: "Ethiopia",
    displayOrder: 2,
  },
  {
    code: "DIRE_DAWA",
    label: "Dire Dawa",
    country: "Ethiopia",
    displayOrder: 3,
  },
];

const CONTAINER_TYPES = [
  { code: "20FT", label: "20FT", sizeFt: 20 },
  { code: "40FT", label: "40FT", sizeFt: 40 },
];

const DEMO_BOOKINGS = [
  {
    reference: "BKG_CONT_001",
    containerCode: "40FT",
    quantity: 20,
    totalWeightTons: 500,
    originCode: "DJIBOUTI",
    destinationCode: "ADDIS_ABABA",
    scheduledDate: "2026-06-20T08:00:00.000Z",
    status: "PAID",
    paymentStatus: "PAID",
  },
  {
    reference: "BKG_ONT_02",
    containerCode: "20FT",
    quantity: 10,
    totalWeightTons: 300,
    originCode: "DJIBOUTI",
    destinationCode: "ADDIS_ABABA",
    scheduledDate: "2026-06-20T08:00:00.000Z",
    status: "PAID",
    paymentStatus: "PAID",
  },
  {
    reference: "BKG_ONT_03",
    containerCode: "40FT",
    quantity: 15,
    totalWeightTons: 450,
    originCode: "DJIBOUTI",
    destinationCode: "ADDIS_ABABA",
    scheduledDate: "2026-06-20T08:00:00.000Z",
    status: "PAID",
    paymentStatus: "PAID",
  },
  {
    reference: "BKG_ONT_07",
    containerCode: "20FT",
    quantity: 6,
    totalWeightTons: 180,
    originCode: "DJIBOUTI",
    destinationCode: "ADDIS_ABABA",
    scheduledDate: "2026-06-20T08:00:00.000Z",
    status: "PAID",
    paymentStatus: "PAID",
  },
  {
    reference: "BKG_ONT_08",
    containerCode: "40FT",
    quantity: 4,
    totalWeightTons: 120,
    originCode: "DJIBOUTI",
    destinationCode: "ADDIS_ABABA",
    scheduledDate: "2026-06-20T08:00:00.000Z",
    status: "PAID",
    paymentStatus: "PAID",
  },
  {
    reference: "BKG_ONT_09",
    containerCode: "20FT",
    quantity: 5,
    totalWeightTons: 110,
    originCode: "DJIBOUTI",
    destinationCode: "ADDIS_ABABA",
    scheduledDate: "2026-06-20T08:00:00.000Z",
    status: "PAID",
    paymentStatus: "PAID",
  },
  {
    reference: "BKG_ONT_04",
    containerCode: "40FT",
    quantity: 12,
    totalWeightTons: 360,
    originCode: "ADDIS_ABABA",
    destinationCode: "DIRE_DAWA",
    scheduledDate: "2026-06-20T08:00:00.000Z",
    status: "PAID",
    paymentStatus: "PAID"
  },
  {
    reference: "BKG_ONT_05",
    containerCode: "20FT",
    quantity: 8,
    totalWeightTons: 160,
    originCode: "DJIBOUTI",
    destinationCode: "ADDIS_ABABA",
    scheduledDate: "2026-06-21T08:00:00.000Z",
    status: "PAID",
    paymentStatus: "PAID",
  },
  {
    reference: "BKG-CONT-006",
    containerCode: "40FT",
    quantity: 80,
    totalWeightTons: 3600,
    originCode: "DJIBOUTI",
    destinationCode: "ADDIS_ABABA",
    scheduledDate: "2026-06-20T08:00:00.000Z",
    status: "PAID",
    paymentStatus: "PAID",
  },
];

const DEMO_BULK_BOOKINGS = [
  {
    reference: "BKG-BULK-001",
    cargoCode: "COFFEE",
    totalWeightTons: 1200,
    originCode: "DJIBOUTI",
    destinationCode: "ADDIS_ABABA",
    scheduledDate: "2026-06-20T08:00:00.000Z",
    status: "PAID",
    paymentStatus: "PAID",
  },
  {
    reference: "BKG-BULK-002",
    cargoCode: "FERTILIZER",
    totalWeightTons: 800,
    originCode: "DJIBOUTI",
    destinationCode: "ADDIS_ABABA",
    scheduledDate: "2026-06-20T08:00:00.000Z",
    status: "PAID",
    paymentStatus: "PAID",
  },
  {
    reference: "BKG-BULK-003",
    cargoCode: "STEEL",
    totalWeightTons: 450,
    originCode: "ADDIS_ABABA",
    destinationCode: "DIRE_DAWA",
    scheduledDate: "2026-06-20T08:00:00.000Z",
    status: "PAID",
    paymentStatus: "PAID",
  },
];

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

  constructor(private readonly dataSource: DataSource) { }

  async run() {
    const shouldSeed = process.env[SEED_FLAG]?.trim().toLowerCase() === "true";
    if (!shouldSeed) {
      this.logger.log(
        `Skipping demo booking seed because ${SEED_FLAG} is not enabled`,
      );
      return;
    }

    await this.dataSource.transaction(async (manager) => {
      await manager.getRepository(WagonType).upsert(
        [
          {
            code: "NW5",
            name: "Flat Wagon",
            capacityTons: 70,
            lengthMeters: 14,
            maxWagonsPerTrain: 53,
            supportedLoadTypes: ["CONTAINER"],
            isActive: true,
            equatedLengthM: 14,
            tareWeightTons: 20,
            supportsContainer: true,
            maxContainerGrossT: 70,
          },
          {
            code: "KW2",
            name: "Covered Hopper",
            capacityTons: 60,
            lengthMeters: 12,
            maxWagonsPerTrain: 55,
            supportedLoadTypes: ["BULK"],
            isActive: true,
            equatedLengthM: 12,
            tareWeightTons: 18,
            supportsContainer: false,
          },
          {
            code: "PW2",
            name: "Powder Wagon",
            capacityTons: 55,
            lengthMeters: 12,
            maxWagonsPerTrain: 55,
            supportedLoadTypes: ["BULK"],
            isActive: true,
            equatedLengthM: 12,
            tareWeightTons: 17,
            supportsContainer: false,
          },
          {
            code: "CW3",
            name: "Open Wagon",
            capacityTons: 65,
            lengthMeters: 13,
            maxWagonsPerTrain: 53,
            supportedLoadTypes: ["BULK"],
            isActive: true,
            equatedLengthM: 13,
            tareWeightTons: 19,
            supportsContainer: false,
          },
        ],
        { conflictPaths: { code: true } },
      );

      await manager.getRepository(Locomotive).upsert(
        [
          {
            code: "LOC-001",
            name: "Demo Locomotive 1",
            locomotiveType: 'ELECTRIC',
            maxPullWeightTons: 3500,
            maxTrainLengthMeters: 760,
            status: "AVAILABLE",
          },
          {
            code: "LOC-002",
            name: "Demo Locomotive 2",
            locomotiveType: 'DIESEL',
            maxPullWeightTons: 2500,
            maxTrainLengthMeters: 760,
            status: "AVAILABLE",
          },
        ],
        { conflictPaths: { code: true } },
      );

      await manager.getRepository(Yard).upsert(
        YARDS.map((yard) => ({ ...yard, isActive: true })),
        { conflictPaths: { code: true } },
      );

      await manager.getRepository(ServiceType).upsert(
        {
          code: SERVICE_TYPE_CODE,
          serviceName: "Rail Container Service",
          description: "Temporary service type for train scheduling demos",
          canBeBookedAlone: true,
          includesFirstMile: false,
          includesLastMile: false,
          includesCustoms: false,
          priorityBonusPoints: 0,
          isActive: true,
          displayOrder: 1,
        },
        { conflictPaths: { code: true } },
      );

      await manager.getRepository(ContainerType).upsert(
        CONTAINER_TYPES.map((containerType, index) => ({
          ...containerType,
          wagonsPerUnit: 1,
          isReefer: false,
          isOpenTop: false,
          isActive: true,
          displayOrder: index + 1,
        })),
        { conflictPaths: { code: true } },
      );

      await manager.getRepository(Company).upsert(
        {
          name: "Train Scheduling Demo Customer",
          type: CompanyType.Customer,
          status: CompanyStatus.Active,
          tin: COMPANY_TIN,
          vatNumber: "1234567890",
          fanNumber: "1234567890123456",
          country: "Ethiopia",
          address: "Demo Address",
          phone: "251900000001",
          email: COMPANY_EMAIL,
          website: null,
          contactPersonName: "Train Scheduling",
          contactPersonPhone: "251900000001",
          generalManagerName: "Demo Manager",
          generalManagerEmail: COMPANY_EMAIL,
          generalManagerPhone: "251900000001",
        },
        { conflictPaths: { tin: true } },
      );

      const [serviceType, company, yards, containerTypes] = await Promise.all([
        manager
          .getRepository(ServiceType)
          .findOneByOrFail({ code: SERVICE_TYPE_CODE }),
        manager
          .getRepository(Company)
          .findOneByOrFail({ tin: COMPANY_TIN }),
        manager.getRepository(Yard).find(),
        manager.getRepository(ContainerType).find(),
      ]);

      const yardByCode = new Map(yards.map((yard) => [yard.code, yard]));
      const containerTypeByCode = new Map(
        containerTypes.map((containerType) => [
          containerType.code,
          containerType,
        ]),
      );

      for (const demoBooking of DEMO_BOOKINGS) {
        const origin = yardByCode.get(demoBooking.originCode);
        const destination = yardByCode.get(demoBooking.destinationCode);
        const containerType = containerTypeByCode.get(
          demoBooking.containerCode,
        );

        if (!origin || !destination || !containerType) {
          throw new Error(
            `demo_booking_seed_dependency_missing:${demoBooking.reference}`,
          );
        }

        const vgmPerUnitTons =
          demoBooking.totalWeightTons / demoBooking.quantity;

        await manager.getRepository(Booking).upsert(
          {
            reference: demoBooking.reference,
            companyId: company.id,
            status: demoBooking.status,
            scheduledDate: new Date(demoBooking.scheduledDate),
            totalAmount: 2,
            paymentStatus: demoBooking.paymentStatus,
            contractType: "NEW",
            serviceTypeId: serviceType.id,
            equipmentReturn: "WITHOUT_RETURN",
            originYardId: origin.id,
            destinationYardId: destination.id,
            tradeDirection: "IMPORT",
            freightType: "CONTAINER",
            cargoTypeId: null,
            cargoFreeText: null,
            shippingLineId: null,
            cargoTotalWeightVgm: demoBooking.totalWeightTons,
            isHazardous: false,
            paymentCurrency: "ETB",            priorityScore: 0,
            versionNumber: 1,
          },
          { conflictPaths: { reference: true } },
        );

        const booking = await manager.getRepository(Booking).findOneByOrFail({
          reference: demoBooking.reference,
        });

        await manager
          .getRepository(BookingContainer)
          .delete({ bookingId: booking.id });
        const wagonsRequired =
          Number(demoBooking.quantity) * Number(containerType.wagonsPerUnit ?? 1);

        await manager.getRepository(BookingContainer).insert({
          id: randomUUID(),
          bookingId: booking.id,
          containerTypeId: containerType.id,
          quantity: demoBooking.quantity,
          vgmPerUnitTons,
          totalVgmTons: demoBooking.totalWeightTons,
          wagonsRequired,
          weightLimitRuleId: null,
          isOverweight: vgmPerUnitTons > 35,
          overweightExcessTons: vgmPerUnitTons > 35 ? vgmPerUnitTons - 35 : null,
        });
      }

      await manager.getRepository(CargoType).upsert(
        [
          { code: "COFFEE", cargoTypeName: "Coffee", isActive: true, displayOrder: 1 },
          { code: "FERTILIZER", cargoTypeName: "Fertilizer", isActive: true, displayOrder: 2 },
          { code: "STEEL", cargoTypeName: "Steel", isActive: true, displayOrder: 3 },
        ],
        { conflictPaths: { code: true } },
      );

      const cargoTypes = await manager.getRepository(CargoType).find();
      const cargoByCode = new Map(cargoTypes.map((c) => [c.code, c]));

      for (const demoBulk of DEMO_BULK_BOOKINGS) {
        const origin = yardByCode.get(demoBulk.originCode);
        const destination = yardByCode.get(demoBulk.destinationCode);
        const cargoType = cargoByCode.get(demoBulk.cargoCode);

        if (!origin || !destination || !cargoType) {
          throw new Error(`demo_bulk_seed_dependency_missing:${demoBulk.reference}`);
        }

        await manager.getRepository(Booking).upsert(
          {
            reference: demoBulk.reference,
            companyId: company.id,
            status: demoBulk.status,
            scheduledDate: new Date(demoBulk.scheduledDate),
            totalAmount: 0,
            paymentStatus: demoBulk.paymentStatus,
            contractType: "NEW",
            serviceTypeId: serviceType.id,
            equipmentReturn: "WITHOUT_RETURN",
            originYardId: origin.id,
            destinationYardId: destination.id,
            tradeDirection: "IMPORT",
            freightType: "BULK",
            cargoTypeId: cargoType.id,
            cargoFreeText: demoBulk.cargoCode,
            shippingLineId: null,
            cargoTotalWeightVgm: demoBulk.totalWeightTons,
            isHazardous: false,
            paymentCurrency: "USD",            priorityScore: 10,
            schedulingStatus: "HOLDING",
            versionNumber: 1,
          },
          { conflictPaths: { reference: true } },
        );
      }

      const djibouti = yardByCode.get("DJIBOUTI");
      const addis = yardByCode.get("ADDIS_ABABA");
      if (djibouti && addis) {
        const routeName = "Djibouti → Addis Ababa";
        let route = await manager.getRepository(Route).findOneBy({ name: routeName });
        if (!route) {
          route = await manager.getRepository(Route).save(
            manager.getRepository(Route).create({
              name: routeName,
              originYardId: djibouti.id,
              destinationYardId: addis.id,
              isActive: true,
            }),
          );
          await manager.getRepository(RouteMilestone).save([
            manager.getRepository(RouteMilestone).create({
              routeId: route.id,
              yardId: djibouti.id,
              sequenceNo: 1,
            }),
            manager.getRepository(RouteMilestone).create({
              routeId: route.id,
              yardId: addis.id,
              sequenceNo: 2,
            }),
          ]);
        }
      }

      const nw5 = await manager.getRepository(WagonType).findOneBy({ code: "NW5" });
      if (nw5 && djibouti && addis) {
        await manager.getRepository(Wagon).upsert(
          Array.from({ length: 20 }, (_, index) => ({
            wagonNumber: `WGN-DEMO-${String(index + 1).padStart(3, "0")}`,
            wagonTypeId: nw5.id,
            trainId: null,
            sequenceNumber: null,
            tareWeight: 20,
            maxPayloadWeight: 70,
            status: WagonStatus.Available,
            currentYardId: index % 2 === 0 ? djibouti.id : addis.id,
            notes: "Demo wagon for train scheduling",
            trainSetWagonId: null,
            currentTrainScheduleId: null,
          })),
          { conflictPaths: { wagonNumber: true } },
        );
      }

      if (djibouti) {
        await manager.getRepository(Locomotive).update(
          { code: "LOC-001" },
          { currentYardId: djibouti.id },
        );
      }
      if (addis) {
        await manager.getRepository(Locomotive).update(
          { code: "LOC-002" },
          { currentYardId: addis.id },
        );
      }

      const ft20 = containerTypeByCode.get("20FT");
      const ft40 = containerTypeByCode.get("40FT");
      if (ft20 && ft40) {
        await manager.getRepository(Container).upsert(
          Array.from({ length: 30 }, (_, index) => {
            const is40Ft = index % 2 === 0;
            return {
              containerNumber: `CONT-DEMO-${String(index + 1).padStart(3, "0")}`,
              containerTypeId: is40Ft ? ft40.id : ft20.id,
              wagonId: null,
              position: null,
              tareWeight: is40Ft ? 4.0 : 2.5,
              maxGrossWeight: is40Ft ? 32.5 : 24.5,
              sealNumber: null,
              status: "AVAILABLE",
              bookingId: null,
              wagonBookingAllocationId: null,
              bookingContainerId: null,
            };
          }),
          { conflictPaths: { containerNumber: true } },
        );
      }
    });

    this.logger.log("Seeded demo train scheduling data");
  }
}
