import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
import {
  ArrayMinSize,
  IsArray,
  IsBoolean,
  IsDateString,
  IsIn,
  IsInt,
  IsNumber,
  IsOptional,
  IsString,
  IsUUID,
  Max,
  MaxLength,
  Min,
  MinLength,
  Validate,
  ValidateIf,
  ValidateNested,
} from 'class-validator';
import { BOOKING_STATUSES, BOOKING_TYPES, FREIGHT_TYPES } from '../entities/booking.entity';
import { BookingFreightShapeConstraint } from './validators/booking-freight.validator';

const CONTRACT_TYPES = ['NEW', 'RENEWAL'] as const;
const EQUIPMENT_RETURNS = ['WITH_RETURN', 'WITHOUT_RETURN', 'NA'] as const;
const TRADE_DIRECTIONS = ['IMPORT', 'EXPORT', 'DOMESTIC'] as const;
const PAYMENT_CURRENCIES = ['ETB', 'USD'] as const;

export {
  BOOKING_STATUSES,
  BOOKING_TYPES,
  CONTRACT_TYPES,
  EQUIPMENT_RETURNS,
  FREIGHT_TYPES,
  TRADE_DIRECTIONS,
  PAYMENT_CURRENCIES,
};

export class CreateBookingContainerDto {
  @ApiProperty({ format: 'uuid', description: 'FK to container_types.id' })
  @IsUUID()
  containerTypeId!: string;

  @ApiProperty({ description: 'Quantity of containers', minimum: 1 })
  @IsInt()
  @Min(1)
  @Transform(({ value }) => Number(value))
  quantity!: number;

  @ApiProperty({ description: 'VGM per container in tons', minimum: 0 })
  @IsNumber()
  @Min(0)
  @Transform(({ value }) => Number(value))
  vgmPerUnitTons!: number;
}

export class CreateContractRouteDto {
  @ApiProperty({ format: 'uuid', description: 'FK to yards.id (origin)' })
  @IsUUID()
  originYardId!: string;

  @ApiProperty({ format: 'uuid', description: 'FK to yards.id (destination)' })
  @IsUUID()
  destinationYardId!: string;

  @ApiPropertyOptional({
    format: 'uuid',
    description: 'Container type for CONTAINER contracts; omit for BULK',
  })
  @IsOptional()
  @IsUUID()
  containerTypeId?: string;

  @ApiProperty({ description: 'Contracted quantity for this route', minimum: 1 })
  @IsNumber()
  @Min(0)
  @Transform(({ value }) => Number(value))
  quantity!: number;

  @ApiPropertyOptional({
    description: 'Road distance (km) for this route; used to bill road orders.',
    minimum: 0,
  })
  @IsOptional()
  @IsNumber()
  @Min(0)
  @Transform(({ value }) =>
    value === undefined || value === null || value === '' ? undefined : Number(value),
  )
  km?: number;
}

export class CreateBookingDto {
  /** Class-level freight shape check (not a request field). */
  @Validate(BookingFreightShapeConstraint)
  freightShapeValidation?: boolean;
  @ApiPropertyOptional({ description: 'Unique booking reference (auto-generated if omitted)' })
  @IsOptional()
  @IsString()
  @Transform(({ value }) => (typeof value === 'string' ? value.trim() : value))
  reference?: string;

  // @ApiPropertyOptional({ format: 'uuid', description: 'Admin only: target customer (legacy)' })
  // @IsOptional()
  // @IsUUID()
  // customerId?: string;

  @ApiPropertyOptional({ description: 'Staff only: government booking flag' })
  @IsOptional()
  @IsBoolean()
  @Transform(({ value }) => value === 'true' || value === true)
  isGovernment?: boolean;

  @ApiPropertyOptional({ description: 'Required when isGovernment is true' })
  @ValidateIf((o) => o.isGovernment === true)
  @IsString()
  @MinLength(2)
  @Transform(({ value }) => (typeof value === 'string' ? value.trim() : value))
  governmentInstitution?: string;

  @ApiPropertyOptional({ format: 'uuid', description: 'Admin only: target company' })
  @ValidateIf((o) => o.isGovernment !== true)
  @IsOptional()
  @IsUUID()
  companyId?: string;

  @ApiPropertyOptional({ format: 'uuid' })
  @IsOptional()
  @IsUUID()
  trainId?: string;

  /**
   * Staff-only manual pin to a specific train. Customers omit this — they pick a
   * DAY via {@link scheduledDate} and the batch engine assigns a train within
   * that (route, day) pool. When provided, the schedule must be OPEN and on the
   * booking route.
   */
  @ApiPropertyOptional({
    format: 'uuid',
    description: 'Staff only: pin to a specific train schedule. Customers omit this.',
  })
  @IsOptional()
  @IsUUID()
  trainScheduleId?: string;

  @ApiPropertyOptional({
    enum: BOOKING_TYPES,
    default: 'ONE_TIME',
    description:
      'ONE_TIME (default) for a normal booking; GENERAL_CONTRACT for an umbrella contract drawn down by orders.',
  })
  @IsOptional()
  @IsIn([...BOOKING_TYPES])
  bookingType?: string;

  /**
   * The day the customer wants to ship (the pool day key). Required for one-time
   * bookings; omitted for general contracts, which pick the date per order.
   */
  @ApiPropertyOptional({ example: '2026-06-15T00:00:00.000Z' })
  @ValidateIf((o) => o.bookingType !== 'GENERAL_CONTRACT')
  @IsDateString()
  scheduledDate?: string;

  @ApiProperty({ enum: CONTRACT_TYPES })
  @IsIn([...CONTRACT_TYPES])
  contractType!: string;

  @ApiPropertyOptional({ format: 'uuid' })
  @IsOptional()
  @IsUUID()
  @Transform(({ value }) => (value === '' || value == null ? undefined : value))
  previousContractId?: string;

  @ApiProperty({ format: 'uuid', description: 'FK to service_types.id' })
  @IsUUID()
  serviceTypeId!: string;

  @ApiPropertyOptional()
  @IsOptional()
  @IsString()
  firstMilePickupAddress?: string;

  @ApiPropertyOptional({ description: 'First-mile pickup latitude (-90..90)' })
  @IsOptional()
  @IsNumber()
  @Min(-90)
  @Max(90)
  @Transform(({ value }) => (value == null || value === '' ? undefined : Number(value)))
  firstMilePickupLat?: number;

  @ApiPropertyOptional({ description: 'First-mile pickup longitude (-180..180)' })
  @IsOptional()
  @IsNumber()
  @Min(-180)
  @Max(180)
  @Transform(({ value }) => (value == null || value === '' ? undefined : Number(value)))
  firstMilePickupLng?: number;

  @ApiPropertyOptional()
  @IsOptional()
  @IsString()
  lastMileDeliveryAddress?: string;

  @ApiPropertyOptional({ description: 'Last-mile delivery latitude (-90..90)' })
  @IsOptional()
  @IsNumber()
  @Min(-90)
  @Max(90)
  @Transform(({ value }) => (value == null || value === '' ? undefined : Number(value)))
  lastMileDeliveryLat?: number;

  @ApiPropertyOptional({ description: 'Last-mile delivery longitude (-180..180)' })
  @IsOptional()
  @IsNumber()
  @Min(-180)
  @Max(180)
  @Transform(({ value }) => (value == null || value === '' ? undefined : Number(value)))
  lastMileDeliveryLng?: number;

  @ApiPropertyOptional({ description: 'Whether EDR handles customs clearance' })
  @IsOptional()
  @IsBoolean()
  @Transform(({ value }) => value === 'true' || value === true)
  customsClearingEnabled?: boolean;

  @ApiPropertyOptional({ maxLength: 200, description: 'Customs clearing agent name (when customs is enabled)' })
  @IsOptional()
  @IsString()
  @MaxLength(200)
  customsClearingAgent?: string;

  @ApiProperty({ enum: EQUIPMENT_RETURNS })
  @IsIn([...EQUIPMENT_RETURNS])
  equipmentReturn!: string;

  @ApiProperty({ format: 'uuid', description: 'FK to yards.id (origin)' })
  @IsUUID()
  originYardId!: string;

  @ApiProperty({ format: 'uuid', description: 'FK to yards.id (destination)' })
  @IsUUID()
  destinationYardId!: string;

  /**
   * GENERAL_CONTRACT only: the routes this contract reserves quantity across.
   * Each entry has its own origin/destination and quantity; the first entry also
   * matches the booking's originYardId/destinationYardId. Omitted for one-time
   * bookings, which use the single origin/destination above.
   */
  @ApiPropertyOptional({ type: [CreateContractRouteDto] })
  @ValidateIf((o) => o.bookingType === 'GENERAL_CONTRACT')
  @IsOptional()
  @IsArray()
  @ArrayMinSize(1)
  @ValidateNested({ each: true })
  @Type(() => CreateContractRouteDto)
  routes?: CreateContractRouteDto[];

  @ApiProperty({ enum: TRADE_DIRECTIONS })
  @IsIn([...TRADE_DIRECTIONS])
  tradeDirection!: string;

  @ApiProperty({ enum: FREIGHT_TYPES, description: 'CONTAINER or BULK (mutually exclusive cargo shape)' })
  @IsIn([...FREIGHT_TYPES])
  freightType!: string;

  @ApiPropertyOptional({
    format: 'uuid',
    description: 'Required for BULK; must be omitted for CONTAINER',
  })
  @ValidateIf((o) => o.freightType === 'BULK')
  @IsUUID()
  cargoTypeId?: string;

  @ApiPropertyOptional({ maxLength: 200 })
  @IsOptional()
  @IsString()
  cargoFreeText?: string;

  @ApiPropertyOptional({ format: 'uuid', description: 'FK to shipping_lines.id' })
  @IsOptional()
  @IsUUID()
  shippingLineId?: string;

  @ApiProperty({ description: 'Total cargo weight VGM in tons', minimum: 0 })
  @IsNumber()
  @Min(0)
  @Transform(({ value }) => Number(value))
  cargoTotalWeightVgm!: number;

  @ApiPropertyOptional({ default: false })
  @IsOptional()
  @IsBoolean()
  @Transform(({ value }) => value === 'true' || value === true)
  isHazardous?: boolean;

  @ApiProperty({ enum: PAYMENT_CURRENCIES })
  @IsIn([...PAYMENT_CURRENCIES])
  paymentCurrency!: string;

  @ApiPropertyOptional()
  @IsOptional()
  @IsString()
  pnrCode?: string;

  @ApiPropertyOptional()
  @IsOptional()
  @IsDateString()
  startDate?: string;

  @ApiPropertyOptional()
  @IsOptional()
  @IsDateString()
  endDate?: string;

  @ApiPropertyOptional()
  @IsOptional()
  @IsString()
  financialTerms?: string;

  @ApiPropertyOptional({
    type: [CreateBookingContainerDto],
    description: 'Required for CONTAINER (min 1 line); must be empty for BULK',
  })
  @ValidateIf((o) => o.freightType === 'CONTAINER')
  @IsArray()
  @ArrayMinSize(1)
  @ValidateNested({ each: true })
  @Type(() => CreateBookingContainerDto)
  containers?: CreateBookingContainerDto[];
}
