import { Inject, Injectable } from "@nestjs/common";
import { In, Not } from "typeorm";

import { CargoType } from "../rule-engine/entities/cargo-type.entity";
import { ContainerType } from "../rule-engine/entities/container-type.entity";
import {
  CARGO_TYPES_REPOSITORY,
  ICargoTypesRepository,
} from "../rule-engine/interfaces/cargo-types.repository.interface";
import {
  CONTAINER_TYPES_REPOSITORY,
  IContainerTypesRepository,
} from "../rule-engine/interfaces/container-types.repository.interface";
import {
  IServiceTypesRepository,
  SERVICE_TYPES_REPOSITORY,
} from "../rule-engine/interfaces/service-types.repository.interface";
import {
  IShippingLinesRepository,
  SHIPPING_LINES_REPOSITORY,
} from "../rule-engine/interfaces/shipping-lines.repository.interface";
import {
  IYardsRepository,
  YARDS_REPOSITORY,
} from "../rule-engine/interfaces/yards.repository.interface";
import {
  BookingReferenceCargoTypeChildDto,
  BookingReferenceCargoTypeGroupDto,
  BookingReferenceContainerSizeGroupDto,
  BookingReferenceContainerTypeDto,
  BookingReferenceDataDto,
  BookingReferenceServiceDto,
  BookingReferenceShippingLineDto,
  BookingReferenceYardDto,
} from "./dto/booking-reference-data.dto";

const LEGACY_YARD_CODES = ["LEGACY_ORIGIN", "LEGACY_DEST"] as const;

export function buildCargoTypeTree(
  rows: CargoType[],
): BookingReferenceCargoTypeGroupDto[] {
  const active = rows.filter((r) => r.isActive);
  const parents = active
    .filter((r) => !r.parentGroupId)
    .sort(
      (a, b) => a.displayOrder - b.displayOrder || a.code.localeCompare(b.code),
    );

  return parents.map((parent) => {
    const children = active
      .filter((r) => r.parentGroupId === parent.id)
      .sort(
        (a, b) =>
          a.displayOrder - b.displayOrder || a.code.localeCompare(b.code),
      )
      .map(
        (child): BookingReferenceCargoTypeChildDto => ({
          id: child.id,
          name: child.cargoTypeName,
          code: child.code,
          show_free_text_box: child.showFreeTextBox,
          unit_of_measure: child.unitOfMeasure ?? null,
        }),
      );

    const group: BookingReferenceCargoTypeGroupDto = {
      id: parent.id,
      name: parent.cargoTypeName,
      code: parent.code,
    };
    if (children.length > 0) {
      group.children = children;
    }
    return group;
  });
}

export function groupContainersBySize(
  rows: ContainerType[],
): BookingReferenceContainerSizeGroupDto[] {
  const active = rows.filter((r) => r.isActive);
  const bySize = new Map<string, ContainerType[]>();

  for (const ct of active) {
    const sizeKey =
      ct.sizeFt != null && ct.sizeFt > 0 ? `${ct.sizeFt}ft` : "other";
    const list = bySize.get(sizeKey) ?? [];
    list.push(ct);
    bySize.set(sizeKey, list);
  }

  const sortSizeKey = (key: string): number => {
    if (key === "other") return Number.MAX_SAFE_INTEGER;
    const n = parseInt(key, 10);
    return Number.isNaN(n) ? Number.MAX_SAFE_INTEGER - 1 : n;
  };

  return [...bySize.entries()]
    .sort(([a], [b]) => sortSizeKey(a) - sortSizeKey(b))
    .map(([size, types]) => ({
      size,
      types: types
        .sort(
          (a, b) =>
            (a.displayOrder ?? 0) - (b.displayOrder ?? 0) ||
            a.code.localeCompare(b.code),
        )
        .map(
          (ct): BookingReferenceContainerTypeDto => ({
            id: ct.id,
            name: ct.label?.trim() ? ct.label : ct.code,
            code: ct.code,
            is_reefer: ct.isReefer ?? false,
            wagons_per_unit: Number(ct.wagonsPerUnit ?? 1),
          }),
        ),
    }));
}

@Injectable()
export class BookingReferenceDataService {
  constructor(
    @Inject(YARDS_REPOSITORY)
    private readonly yardsRepository: IYardsRepository,
    @Inject(CONTAINER_TYPES_REPOSITORY)
    private readonly containerTypesRepository: IContainerTypesRepository,
    @Inject(SERVICE_TYPES_REPOSITORY)
    private readonly serviceTypesRepository: IServiceTypesRepository,
    @Inject(SHIPPING_LINES_REPOSITORY)
    private readonly shippingLinesRepository: IShippingLinesRepository,
    @Inject(CARGO_TYPES_REPOSITORY)
    private readonly cargoTypesRepository: ICargoTypesRepository,
  ) { }

  async getReferenceData(): Promise<BookingReferenceDataDto> {
    const [yards, containerTypes, serviceTypes, shippingLines, cargoTypes] =
      await Promise.all([
        this.yardsRepository.findAll({
          where: {
            isActive: true,
            code: Not(In([...LEGACY_YARD_CODES])),
          },
          order: { displayOrder: "ASC", code: "ASC" },
        }),
        this.containerTypesRepository.findAll({
          where: { isActive: true },
          order: { displayOrder: "ASC", code: "ASC" },
        }),
        this.serviceTypesRepository.findAll({
          where: { isActive: true },
          order: { displayOrder: "ASC", code: "ASC" },
        }),
        this.shippingLinesRepository.findAll({
          where: { isActive: true },
          order: { label: "ASC", code: "ASC" },
        }),
        this.cargoTypesRepository.findAll({
          where: { isActive: true },
          order: { displayOrder: "ASC", code: "ASC" },
        }),
      ]);

    return {
      yard: yards.map(
        (y): BookingReferenceYardDto => ({
          id: y.id,
          name: y.label,
          code: y.code,
          country: y.country,
        }),
      ),
      containers: groupContainersBySize(containerTypes),
      service: serviceTypes.map(
        (s): BookingReferenceServiceDto => ({
          name: s.serviceName,
          ...s,
        }),
      ),
      shipping_line: shippingLines.map(
        (sl): BookingReferenceShippingLineDto => ({
          id: sl.id,
          name: sl.label,
          code: sl.code,
        }),
      ),
      cargo_type: buildCargoTypeTree(cargoTypes),
    };
  }
}
