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

/**
 * Tax-snapshot fields shared by every quote line-item DTO. Mirrors the
 * booking-side TaxSnapshotFieldsDto. 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. Not persisted.
 */
abstract class QuoteTaxSnapshotFieldsDto {
  @IsObject() @IsOptional() tax_breakdown?: Record<string, Record<string, number>> | null;
  @IsNumber() @IsOptional() tax_amount?: number | null;
  @IsBoolean() @IsOptional() refresh_tax?: boolean;
}

export class CreateHotelItemDto extends QuoteTaxSnapshotFieldsDto {
  @IsUUID() @IsNotEmpty() hotel_id: string;
  @IsDateString() @IsNotEmpty() check_in_date: string;
  @IsDateString() @IsNotEmpty() check_out_date: string;
  @IsNumber() @IsNotEmpty() nights: number;
  /** Recomputed server-side from periods + extras; incoming value is
   *  accepted but overwritten before persist. */
  @IsNumber() @IsOptional() subtotal?: number;
  /** Per-stay free-text remarks — same shape as transport/activity
   *  notes. Carried across to booking_hotel_items on conversion. */
  @IsString() @IsOptional() @MaxLength(500) notes?: string;

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

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

export class CreateTransportItemDto extends QuoteTaxSnapshotFieldsDto {
  @IsUUID() @IsNotEmpty() transport_service_id: string;
  // FK into vehicle_types. Optional/nullable since SIC lines have no
  // vehicle axis (the line's is_shared flag flips the matrix slot).
  @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. */
  @IsInt() @IsOptional() @Min(0) adults?: number | null;
  @IsInt() @IsOptional() @Min(0) children?: number | null;
  @IsNumber() @IsOptional() unit_price?: number;
  @IsNumber() @IsOptional() subtotal?: number;
  @IsDateString() @IsOptional() date?: string;
  // Pickup + dropoff times for the transport leg (HH:MM, 24h). Mirror
  // the booking-side fields so a quote can carry the full trip timing
  // straight into the PDF and pre-fill the booking on conversion.
  @IsString() @IsOptional() @MaxLength(10) pickup_time?: string;
  @IsString() @IsOptional() @MaxLength(10) dropoff_time?: string;
  @IsNumber() @IsOptional() sort_order?: number;
  @IsString() @IsOptional() notes?: string;
  // Per-row pickup + drop-off addresses. Auto-populated from the picked
  // Transport Master (from_address > from_city); ops can override
  // inline. Carried across to booking_transport_items on conversion so
  // operational records inherit the agreed locations.
  @IsString() @IsOptional() @MaxLength(500) pickup_address?: string;
  @IsString() @IsOptional() @MaxLength(500) dropoff_address?: string;
  @IsBoolean() @IsOptional() is_shared?: boolean;
  /**
   * "Dedicated transport for activity" link. The FE may send either:
   *   - linked_activity_id: an existing UUID (when re-saving a quote
   *     where the linked activity already has a persisted id), OR
   *   - linked_activity_link_key: a transient string the FE also
   *     assigned to one of the activity_items in the same payload
   *     (used on first save when neither row has a UUID yet).
   * The save flow resolves link_key → saved activity UUID after the
   * 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 CreateActivityItemDto extends QuoteTaxSnapshotFieldsDto {
  @IsUUID() @IsNotEmpty() activity_id: string;
  @IsUUID() @IsOptional() ticket_id?: string;
  @IsString() @IsNotEmpty() age_group: string;
  // Allow zero — the FE may send a quantity-0 skeleton row to keep
  // an activity persisted even when ops hasn't filled pax counts yet
  // (e.g. activity newly added with a bundled transport but no pax).
  // Without this the activity would silently drop from the payload.
  @IsNumber() @IsOptional() @Min(0) quantity?: number;
  @IsNumber() @IsOptional() unit_price?: number;
  @IsNumber() @IsOptional() subtotal?: number;
  @IsString() @IsOptional() slot?: string;
  @IsDateString() @IsOptional() date?: string;
  @IsNumber() @IsOptional() sort_order?: number;
  @IsString() @IsOptional() notes?: 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 CreateSpecialItemDto extends QuoteTaxSnapshotFieldsDto {
  @IsString() @IsNotEmpty() @MaxLength(255) service_name: string;
  @IsNumber() @IsNotEmpty() total_price: number;
  @IsDateString() @IsOptional() date?: string;
  @IsNumber() @IsOptional() sort_order?: number;
  @IsString() @IsOptional() comments?: string;
  @IsBoolean() @IsOptional() is_day_item?: boolean;
}

export class CreateQuoteDto {
  @IsNumber() @IsNotEmpty() @Min(1) quote_number: number;

  /** 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;
  @IsString() @IsOptional() @MaxLength(100) label?: string;
  @IsBoolean() @IsOptional() is_selected?: boolean;
  @IsString() @IsOptional() notes?: string;

  // Markup & Selling Price
  @IsString() @IsOptional() pricing_strategy?: string;
  @IsUUID() @IsOptional() selling_currency_id?: string;
  @IsNumber() @IsOptional() exchange_rate?: number;
  @IsString() @IsOptional() markup_type?: string;
  @IsNumber() @IsOptional() rounding?: number;
  @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() transport_excluded_pax?: number;
  @IsNumber() @IsOptional() selling_total?: number;
  @IsString() @IsOptional() internal_comments?: string;
  @IsString() @IsOptional() customer_remarks?: string;

  /**
   * Justification text for any auto-generated Change Request raised by
   * this save (zero-priced services, below-floor transport). Optional
   * because admin / approver saves don't trigger CRs and don't need a
   * remark; sent by the FE only when at least one violation is detected
   * before submit. Stored on the CR row's requester_remarks column so
   * managers see why the override was needed.
   */
  @IsString() @IsOptional() change_request_remarks?: string;

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

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

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

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