import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
import {
  ArrayMinSize,
  IsArray,
  IsBoolean,
  IsIn,
  IsNumber,
  IsOptional,
  IsString,
  IsUUID,
  Max,
  MaxLength,
  Min,
  MinLength,
  ValidateIf,
  ValidateNested,
} from 'class-validator';

import { HAZARD_CLASS_VALUES } from '@edr/types';

import { CONTRACT_KINDS } from '../entities/contract.entity';

const TRADE_DIRECTIONS = ['IMPORT', 'EXPORT', 'DOMESTIC'] as const;
const FREIGHT_TYPES = ['CONTAINER', 'BULK'] as const;
const PAYMENT_CURRENCIES = ['ETB', 'USD'] as const;
// Canonical UPPERCASE — everything downstream (booking gating, pricing
// surcharge, GL/portal booking forms) compares contract.equipmentReturn
// against 'WITH_RETURN'/'WITHOUT_RETURN'. Lowercase input is normalized.
const EQUIPMENT_RETURNS = ['WITH_RETURN', 'WITHOUT_RETURN'] as const;

export {
  CONTRACT_KINDS,
  TRADE_DIRECTIONS,
  FREIGHT_TYPES,
  PAYMENT_CURRENCIES,
  EQUIPMENT_RETURNS,
};

/** One cargo-scope row — a container size OR a bulk commodity. NO quantities. */
export class CreateContractCargoScopeDto {
  @ApiPropertyOptional({ description: '"20ft" | "40ft"; omit for bulk' })
  @IsOptional()
  @IsString()
  @MaxLength(10)
  containerSize?: string | null;

  @ApiPropertyOptional({ format: 'uuid', description: 'FK to cargo_types.id' })
  @IsOptional()
  @IsUUID()
  cargoTypeId?: string | null;

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

  @ApiPropertyOptional({
    description:
      'GENERAL only: total bookable quantity for this line (containers per size, or tons/items for bulk). Omit for uncapped.',
    minimum: 1,
  })
  @IsOptional()
  @IsNumber()
  @Min(1)
  @Transform(({ value }) => (value == null || value === '' ? null : Number(value)))
  quantityCap?: number | null;
}

/** A contracted lane (origin → destination). Routes carry NO quantity. */
export class CreateContractRouteInputDto {
  @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({
    description: 'Road billing distance (km); null for rail-only.',
    minimum: 0,
  })
  @IsOptional()
  @IsNumber()
  @Min(0)
  @Transform(({ value }) =>
    value === undefined || value === null || value === '' ? undefined : Number(value),
  )
  km?: number;

  @ApiPropertyOptional({ minimum: 0 })
  @IsOptional()
  @IsNumber()
  @Min(0)
  @Transform(({ value }) =>
    value === undefined || value === null || value === '' ? undefined : Number(value),
  )
  sortOrder?: number;
}

export class CreateContractDto {
  @ApiPropertyOptional({ description: 'Unique contract reference (auto-generated if omitted)' })
  @IsOptional()
  @IsString()
  @Transform(({ value }) => (typeof value === 'string' ? value.trim() : value))
  reference?: string;

  @ApiPropertyOptional({ description: 'Staff only: government contract 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',
    description:
      'Explicit company profile to stamp the contract to (a forwarder contract); ' +
      'commercial contracts otherwise auto-resolve from trade direction.',
  })
  @IsOptional()
  @IsUUID()
  companyProfileId?: string;

  @ApiProperty({ enum: CONTRACT_KINDS, description: 'ONE_TIME | GENERAL' })
  @IsIn([...CONTRACT_KINDS])
  contractKind!: string;

  @ApiPropertyOptional({ format: 'uuid', description: 'FK to contracts.id when renewing' })
  @IsOptional()
  @IsUUID()
  @Transform(({ value }) => (value === '' || value == null ? undefined : value))
  renewalOfId?: string;

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

  @ApiProperty({ enum: FREIGHT_TYPES })
  @IsIn([...FREIGHT_TYPES])
  freightType!: string;

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

  /**
   * Deprecated at the contract level. A contract now always quotes in USD; the
   * customer picks the billing currency per booking (or on the shipment request
   * when GL books on their behalf). Accepted but ignored on create so older
   * clients don't break — the service forces USD.
   */
  @ApiPropertyOptional({
    enum: PAYMENT_CURRENCIES,
    deprecated: true,
    description: 'Ignored — contracts always quote in USD. Choose currency at booking.',
  })
  @IsOptional()
  @IsIn([...PAYMENT_CURRENCIES])
  paymentCurrency?: string;

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

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

  @ApiPropertyOptional({ enum: EQUIPMENT_RETURNS })
  @IsOptional()
  @Transform(({ value }) =>
    typeof value === 'string' ? value.toUpperCase() : value,
  )
  @IsIn([...EQUIPMENT_RETURNS])
  equipmentReturn?: 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({ default: false, description: 'Sets contracts.is_hazardous' })
  @IsOptional()
  @IsBoolean()
  @Transform(({ value }) => value === 'true' || value === true)
  isHazardous?: boolean;

  @ApiPropertyOptional({
    enum: HAZARD_CLASS_VALUES,
    description: 'UN/ADR dangerous-goods class. Required when isHazardous.',
  })
  @ValidateIf((o: CreateContractDto) => o.isHazardous === true)
  @IsIn(HAZARD_CLASS_VALUES, {
    message: `hazardClass must be one of: ${HAZARD_CLASS_VALUES.join(', ')}`,
  })
  hazardClass?: string;

  @ApiPropertyOptional({
    description: 'UN number of the dangerous good. Required when isHazardous.',
  })
  @ValidateIf((o: CreateContractDto) => o.isHazardous === true)
  @IsString()
  @MinLength(1)
  @MaxLength(16)
  @Transform(({ value }) =>
    typeof value === 'string' ? value.trim().toUpperCase() : value,
  )
  unNumber?: string;

  @ApiPropertyOptional({ default: false, description: 'Sets contracts.is_reefer' })
  @IsOptional()
  @IsBoolean()
  @Transform(({ value }) => value === 'true' || value === true)
  isReefer?: boolean;

  @ApiPropertyOptional({ description: 'Contract document type (SPOT, etc.)' })
  @IsOptional()
  @IsString()
  @MaxLength(20)
  contractType?: string;

  @ApiProperty({
    type: [CreateContractCargoScopeDto],
    description:
      'Cargo scope rows. CONTAINER: ≥1 size row. BULK: exactly one cargo-type row.',
  })
  @IsArray()
  @ArrayMinSize(1)
  @ValidateNested({ each: true })
  @Type(() => CreateContractCargoScopeDto)
  cargoScope!: CreateContractCargoScopeDto[];

  @ApiProperty({
    type: [CreateContractRouteInputDto],
    description: 'Contracted lanes. ONE_TIME: exactly 1. GENERAL: 1..N.',
  })
  @IsArray()
  @ArrayMinSize(1)
  @ValidateNested({ each: true })
  @Type(() => CreateContractRouteInputDto)
  routes!: CreateContractRouteInputDto[];
}
