import {
  IsOptional,
  IsString,
  IsUUID,
  IsNumber,
  IsArray,
  IsBoolean,
  IsDateString,
  IsObject,
  ValidateNested,
  MaxLength,
  Min,
  IsIn,
  IsInt,
} from 'class-validator';
import { Type } from 'class-transformer';
import { PricingStrategy, MarkupType } from '../../../entities/query-quote.entity';
import {
  HotelPeriodDto,
  HotelExtraDto,
} from '../../../common/dto/hotel-per-night.dto';

/**
 * Tax-snapshot fields shared by every line-item DTO. Q1=C / Q2=B /
 * Q3=A policy lives in TaxSnapshotService — DTOs just transport the
 * frozen breakdown + computed amount + the transient refresh flag.
 *
 *   tax_breakdown   pass-through (server preserves unless refresh_tax)
 *   tax_amount      pass-through (server recomputes on save anyway)
 *   refresh_tax     transient: when true, server re-snapshots from
 *                   the destination's current slab on this save. Not
 *                   persisted on the row.
 */
abstract class TaxSnapshotFieldsDto {
  @IsObject() @IsOptional() tax_breakdown?: Record<string, Record<string, number>> | null;
  @IsNumber() @IsOptional() tax_amount?: number | null;
  @IsBoolean() @IsOptional() refresh_tax?: boolean;
}

export class UpdateBookingHotelItemDto extends TaxSnapshotFieldsDto {
  @IsUUID() hotel_id: string;
  @IsDateString() check_in_date: string;
  @IsDateString() check_out_date: string;
  @IsNumber() nights: number;
  /** Recomputed server-side from periods + extras; incoming value is
   *  accepted but overwritten before persist. */
  @IsNumber() @IsOptional() subtotal?: number;
  @IsString() @IsOptional() @MaxLength(1000) notes?: string;

  @IsArray()
  @IsOptional()
  @ValidateNested({ each: true })
  @Type(() => HotelPeriodDto)
  periods?: HotelPeriodDto[];

  @IsArray()
  @IsOptional()
  @ValidateNested({ each: true })
  @Type(() => HotelExtraDto)
  extras?: HotelExtraDto[];
}

export class UpdateBookingTransportItemDto extends TaxSnapshotFieldsDto {
  @IsUUID() transport_service_id: string;
  // FK into vehicle_types. Optional/nullable for SIC lines.
  @IsUUID() @IsOptional() vehicle_type_id?: string | null;
  @IsNumber() @IsOptional() @Min(1) quantity?: number;
  /** Pax on this row (null = full party); capped at the query totals.
   *  Same columns the Movement Dashboard edits for partial cancels, so
   *  an itinerary save no longer wipes them. */
  @IsInt() @IsOptional() @Min(0) adults_override?: number | null;
  @IsInt() @IsOptional() @Min(0) children_override?: number | null;
  @IsNumber() @IsOptional() unit_price?: number;
  @IsNumber() @IsOptional() subtotal?: number;
  @IsDateString() @IsOptional() date?: string;
  @IsNumber() @IsOptional() sort_order?: number;
  @IsString() @IsOptional() @MaxLength(1000) notes?: string;
  @IsUUID() @IsOptional() supplier_id?: string;
  @IsUUID() @IsOptional() driver_id?: string;
  @IsUUID() @IsOptional() vehicle_id?: string;
  // Free-text fallbacks — used when the supplier/driver/vehicle isn't a
  // registered master record yet. Real-world flow: OM assigns supplier
  // inline, then WhatsApps the supplier for the customer's pickup details;
  // supplier replies with a driver name + phone + vehicle number which the
  // OM types in without having to create DB rows for every ad-hoc supplier.
  @IsString() @IsOptional() @MaxLength(255) supplier_name_text?: string | null;
  @IsString() @IsOptional() @MaxLength(255) driver_name_text?: string | null;
  @IsString() @IsOptional() @MaxLength(20) driver_phone_text?: string | null;
  @IsString() @IsOptional() @MaxLength(50) vehicle_no_text?: string | null;
  @IsString() @IsOptional() @MaxLength(10) pickup_time?: string;
  @IsString() @IsOptional() @MaxLength(10) dropoff_time?: string;
  // Return-leg pickup time for two-way routes (HH:MM). Movement
  // Dashboard writes this via assignTransportItem; the booking-
  // itinerary save MUST round-trip it or the delete-and-replace
  // path silently wipes the column.
  @IsString() @IsOptional() @MaxLength(10) return_pickup_time?: string;
  @IsString() @IsOptional() @MaxLength(500) pickup_address?: string;
  @IsString() @IsOptional() @MaxLength(500) dropoff_address?: string;
  @IsString() @IsOptional() @MaxLength(20) assignment_status?: string;
  @IsBoolean() @IsOptional() is_shared?: boolean;
  // Transient flag — when true, ops asked to promote driver_name_text
  // + driver_phone_text into a real drivers master row. Backend does
  // the find-or-create (dedupe by phone, then by case-insensitive name
  // at the same supplier) and links driver_id on the saved item.
  @IsBoolean() @IsOptional() sync_driver_to_masters?: boolean;
  /**
   * "Dedicated transport for activity" link. The FE may send either:
   *   - linked_activity_id: an existing UUID (re-save where the
   *     linked activity already has a persisted id), OR
   *   - linked_activity_link_key: a transient string the FE assigned
   *     to one of the activity_items in the same payload (used when
   *     the activity is freshly added in the same save).
   * Save flow resolves link_key → saved activity UUID after activities
   * are inserted, then patches the FK on the transport row.
   */
  @IsUUID() @IsOptional() linked_activity_id?: string;
  @IsString() @IsOptional() linked_activity_link_key?: string;
}

export class UpdateBookingActivityItemDto extends TaxSnapshotFieldsDto {
  @IsUUID() activity_id: string;
  @IsUUID() @IsOptional() ticket_id?: string;
  @IsString() @MaxLength(50) age_group: string;
  // Allow zero — same rationale as CreateActivityItemDto: the FE may
  // send a 0-pax skeleton row to preserve an activity that has a
  // bundled transport but no pax counts entered yet.
  @IsNumber() @IsOptional() @Min(0) quantity?: number;
  @IsNumber() @IsOptional() unit_price?: number;
  @IsNumber() @IsOptional() subtotal?: number;
  @IsDateString() @IsOptional() date?: string;
  @IsNumber() @IsOptional() sort_order?: number;
  @IsString() @IsOptional() @MaxLength(1000) notes?: string;
  @IsUUID() @IsOptional() supplier_id?: string;
  // Free-text supplier fallbacks — same pattern as transport items
  // so ops can record ad-hoc supplier name + phone inline for an
  // activity that isn't tied to a registered supplier yet.
  @IsString() @IsOptional() @MaxLength(255) supplier_name_text?: string | null;
  @IsString() @IsOptional() @MaxLength(20) supplier_phone_text?: string | null;
  @IsString() @IsOptional() @MaxLength(100) slot?: string;
  @IsString() @IsOptional() @MaxLength(20) assignment_status?: string;
  /** Transient identifier the FE assigns so transport items in the
   *  same payload can target this activity via linked_activity_link_key
   *  before the activity has a persisted UUID. Not stored. */
  @IsString() @IsOptional() link_key?: string;
}

export class UpdateBookingSpecialItemDto extends TaxSnapshotFieldsDto {
  @IsString() @MaxLength(255) service_name: string;
  @IsNumber() total_price: number;
  @IsDateString() @IsOptional() date?: string;
  @IsNumber() @IsOptional() sort_order?: number;
  @IsString() @IsOptional() @MaxLength(1000) comments?: string;
}

/**
 * Flight detail row attached to a booking. Multiple rows allowed per
 * booking (typical case: arrival + departure). `transport_item_id`
 * links the flight to a specific transport item when available — the
 * Movement Planner uses that link to surface flight info on hover.
 */
export class UpdateBookingFlightDetailDto {
  @IsUUID() @IsOptional() transport_item_id?: string | null;
  @IsString() @IsOptional() @MaxLength(50) direction?: string | null;
  @IsString() @MaxLength(50) flight_number: string;
  @IsString() @IsOptional() @MaxLength(100) airline?: string | null;
  @IsString() @IsOptional() @MaxLength(10) origin_airport?: string | null;
  @IsString() @IsOptional() @MaxLength(10) destination_airport?: string | null;
  @IsString() @IsOptional() @MaxLength(25) departure_time?: string | null;
  @IsString() @IsOptional() @MaxLength(25) arrival_time?: string | null;
  @IsString() @IsOptional() @MaxLength(50) terminal?: string | null;
  @IsString() @IsOptional() @MaxLength(50) gate?: string | null;
  @IsString() @IsOptional() @MaxLength(100) pnr?: string | null;
  @IsString() @IsOptional() notes?: string | null;
}

export class UpdateBookingDto {
  // Basic details
  @IsString() @IsOptional() @MaxLength(255) guest_name?: string;
  @IsString() @IsOptional() @MaxLength(20) guest_mobile?: string;
  @IsString() @IsOptional() @MaxLength(255) guest_email?: string;
  @IsString() @IsOptional() @MaxLength(20) guest_alt_phone?: string;
  @IsDateString() @IsOptional() start_date?: string;
  @IsDateString() @IsOptional() end_date?: string;
  @IsNumber() @IsOptional() @Min(1) adults?: number;
  @IsArray() @IsOptional() children_ages?: number[];
  @IsNumber() @IsOptional() infants?: number;

  @IsString() @IsOptional() @MaxLength(2000) notes?: string;
  @IsString() @IsOptional() @MaxLength(20) status?: string;
  @IsArray()
  @IsOptional()
  @IsUUID(undefined, { each: true })
  sales_team?: string[];

  @IsUUID() @IsOptional() source_id?: string;

  @IsArray() @IsOptional() source_tags?: string[];

  @IsArray() @IsOptional() remark_tags?: string[];

  // Selling price fields
  @IsUUID() @IsOptional() selling_currency_id?: string;
  @IsNumber() @IsOptional() exchange_rate?: number;
  @IsNumber() @IsOptional() selling_total?: number;
  @IsIn(Object.values(PricingStrategy)) @IsOptional() pricing_strategy?: PricingStrategy;
  @IsIn(Object.values(MarkupType)) @IsOptional() markup_type?: MarkupType;
  @IsNumber() @IsOptional() markup_amount?: number;
  @IsNumber() @IsOptional() hotel_markup?: number;
  @IsNumber() @IsOptional() transport_markup?: number;
  @IsNumber() @IsOptional() activity_markup?: number;
  @IsNumber() @IsOptional() special_markup?: number;
  @IsNumber() @IsOptional() @Min(0) rounding?: number;
  @IsInt() @IsOptional() @Min(0) transport_excluded_pax?: number;
  @IsString() @IsOptional() @MaxLength(2000) internal_comments?: string;
  @IsString() @IsOptional() @MaxLength(2000) customer_remarks?: string;

  /** Quote-level tax config — chosen slab (FK to tax_slabs) for the
   *  Markup panel's tax column. NULL/omitted = "No tax". */
  @IsUUID() @IsOptional() tax_slab_id?: string | null;
  /** When true the markup totals already contain tax; otherwise tax
   *  is added on top at Final Price. */
  @IsBoolean() @IsOptional() tax_inclusive?: boolean;

  @IsArray()
  @IsOptional()
  @ValidateNested({ each: true })
  @Type(() => UpdateBookingHotelItemDto)
  hotel_items?: UpdateBookingHotelItemDto[];

  @IsArray()
  @IsOptional()
  @ValidateNested({ each: true })
  @Type(() => UpdateBookingTransportItemDto)
  transport_items?: UpdateBookingTransportItemDto[];

  @IsArray()
  @IsOptional()
  @ValidateNested({ each: true })
  @Type(() => UpdateBookingActivityItemDto)
  activity_items?: UpdateBookingActivityItemDto[];

  @IsArray()
  @IsOptional()
  @ValidateNested({ each: true })
  @Type(() => UpdateBookingSpecialItemDto)
  special_items?: UpdateBookingSpecialItemDto[];

  @IsArray()
  @IsOptional()
  @ValidateNested({ each: true })
  @Type(() => UpdateBookingFlightDetailDto)
  flight_details?: UpdateBookingFlightDetailDto[];
}
