/**
 * Floor-price guard — finds transport lines whose unit_price falls
 * below the configured floor for the active season. Shared by both
 * BookingService.update and QuoteService.updateQuote so the rule
 * lives in exactly one place.
 *
 * Usage:
 *   const offenders = await findBelowFloorLines(manager, tenantId, transportItems);
 *   if (offenders.length > 0) throw new ForbiddenException(formatBelowFloorError(offenders));
 *
 * Lookup is keyed by (tenant, transport_service_id, vehicle_type_id|null,
 * mode, currency_id) — same identity the transport_pricing PVT/SIC
 * unique indexes use. Currency is resolved via the destination FK.
 *
 * Missing floor config → line is skipped (NULL floor = "no floor for
 * that season"). Missing date → line is skipped (no season to resolve).
 */

import { type EntityManager } from 'typeorm';

/**
 * Polymorphic offender — covers both transport and activity below-floor
 * lines so QuoteService / BookingService can merge results from both
 * guards into a single Change Request payload. Approval UI branches
 * on `category` to render the right detail line.
 */
export interface BelowFloorOffender {
  /** Discriminator. */
  category: 'transport' | 'activity';
  /** Short label for error messages — "Dubai → AUH (PVT, SUV)" or "Activity X (Adult)". */
  label: string;
  selling: number;
  floor: number;
  season_name: string;
  // Transport-specific identity (kept for back-compat with the BE
  // helper consumers + Approvals UI). Activity offenders fill these
  // with the activity_id / ticket_id so payload deserialisers don't
  // need to know about a second shape.
  transport_service_id: string;
  vehicle_type_id: string | null;
  mode: 'PVT' | 'SIC';
}

interface SeasonRange {
  index: number;
  season_name: string;
  start_date: string; // MM-DD
  end_date: string; // MM-DD
}

interface PricingRow {
  transport_service_id: string;
  vehicle_type_id: string | null;
  mode: 'PVT' | 'SIC';
  destination_id: string;
  floor_default_price: string | null;
  floor_season_1_price: string | null;
  floor_season_2_price: string | null;
  floor_season_3_price: string | null;
  floor_season_4_price: string | null;
  floor_season_5_price: string | null;
}

function findSeasonIndex(seasons: SeasonRange[], date: string): SeasonRange | null {
  if (!date || date.length < 10) return null;
  const mmdd = date.substring(5);
  for (const s of seasons) {
    if (!s.start_date || !s.end_date) continue;
    const match =
      s.start_date <= s.end_date
        ? mmdd >= s.start_date && mmdd <= s.end_date
        : mmdd >= s.start_date || mmdd <= s.end_date;
    if (match) return s;
  }
  return null;
}

interface TransportItemLine {
  transport_service_id?: string | null;
  vehicle_type_id?: string | null;
  date?: string | null;
  unit_price?: number | string | null;
  is_shared?: boolean | null;
}

export async function findBelowFloorLines(
  manager: EntityManager,
  tenantId: string,
  transportItems: TransportItemLine[] | null | undefined,
): Promise<BelowFloorOffender[]> {
  type ValidLine = TransportItemLine & {
    transport_service_id: string;
    date: string;
    unit_price: number | string;
  };
  const lines = (transportItems || []).filter((l): l is ValidLine => {
    if (!l?.transport_service_id) return false;
    if (!l?.date) return false;
    if (!(Number(l?.unit_price) > 0)) return false;
    const isShared = l?.is_shared === true;
    if (!isShared && !l?.vehicle_type_id) return false; // PVT needs vehicle_type_id
    return true;
  });
  if (lines.length === 0) return [];

  const serviceIds = [...new Set(lines.map((l) => l.transport_service_id as string))];

  const pricingRows: PricingRow[] = await manager.query(
    `SELECT tp.transport_service_id, tp.vehicle_type_id, tp.mode, tp.destination_id,
            tp.floor_default_price,
            tp.floor_season_1_price, tp.floor_season_2_price, tp.floor_season_3_price,
            tp.floor_season_4_price, tp.floor_season_5_price
     FROM transport_pricing tp
     WHERE tp.tenant_id = $1
       AND tp.is_deleted = false
       AND tp.transport_service_id = ANY($2::uuid[])`,
    [tenantId, serviceIds],
  );

  // Index pricing by (service, vehicle_type_id_or_empty, mode).
  // SIC rows use '' since vehicle_type_id is NULL.
  const pricingByKey = new Map<string, PricingRow>();
  for (const r of pricingRows) {
    const vehKey = r.vehicle_type_id ?? '';
    pricingByKey.set(`${r.transport_service_id}|${vehKey}|${r.mode}`, r);
  }

  // Load seasons per destination once. Multiple lines may share a
  // destination so we cache the load.
  const destinationIds = [...new Set(pricingRows.map((r) => r.destination_id))];
  const seasonsByDest = new Map<string, SeasonRange[]>();
  if (destinationIds.length > 0) {
    const seasonRows: Array<{
      destination_id: string;
      sort_order: number;
      name: string;
      start_date: string;
      end_date: string;
    }> = await manager.query(
      `SELECT destination_id, sort_order, name, start_date, end_date
       FROM seasons
       WHERE tenant_id = $1
         AND destination_id = ANY($2::uuid[])
         AND is_deleted = false
         AND is_active = true
       ORDER BY destination_id, sort_order ASC`,
      [tenantId, destinationIds],
    );
    for (const r of seasonRows) {
      const list = seasonsByDest.get(r.destination_id) || [];
      list.push({
        index: list.length + 1,
        season_name: r.name,
        start_date: r.start_date,
        end_date: r.end_date,
      });
      seasonsByDest.set(r.destination_id, list);
    }
  }

  const offenders: BelowFloorOffender[] = [];

  for (const l of lines) {
    const isShared = l.is_shared === true;
    const mode: 'PVT' | 'SIC' = isShared ? 'SIC' : 'PVT';
    const vehKey = isShared ? '' : (l.vehicle_type_id as string);
    const pricing = pricingByKey.get(`${l.transport_service_id}|${vehKey}|${mode}`);
    if (!pricing) continue; // no pricing row → no floor → no guard

    const seasons = seasonsByDest.get(pricing.destination_id) || [];
    const season = findSeasonIndex(seasons, l.date);

    let floor: number | null = null;
    let seasonName = 'all season';
    if (season) {
      const v = (pricing as unknown as Record<string, string | null>)[
        `floor_season_${season.index}_price`
      ];
      if (v != null) {
        floor = Number(v);
        seasonName = season.season_name;
      }
    }
    if (floor == null && pricing.floor_default_price != null) {
      floor = Number(pricing.floor_default_price);
    }
    if (floor == null) continue; // no floor configured → skip

    const selling = Number(l.unit_price);
    if (selling < floor) {
      const labelType = isShared ? 'SIC' : `PVT ${String(l.vehicle_type_id || '').slice(0, 6)}…`;
      offenders.push({
        category: 'transport',
        label: `${labelType} on ${String(l.transport_service_id).slice(0, 6)}… (${l.date})`,
        selling,
        floor,
        season_name: seasonName,
        transport_service_id: l.transport_service_id,
        vehicle_type_id: isShared ? null : (l.vehicle_type_id as string),
        mode,
      });
    }
  }

  return offenders;
}

/**
 * Compose a user-facing error string from a list of offenders. Mixed
 * transport + activity offenders are grouped with category prefixes
 * so the approver sees what's involved at a glance.
 */
export function formatBelowFloorError(offenders: BelowFloorOffender[]): string {
  const sample = offenders
    .slice(0, 2)
    .map(
      (o) =>
        `${o.category === 'activity' ? 'Activity' : 'Transport'} — ${o.label} (selling ${o.selling} < floor ${o.floor}, ${o.season_name})`,
    )
    .join('; ');
  const more = offenders.length > 2 ? ` + ${offenders.length - 2} more` : '';
  const plural = offenders.length === 1 ? '' : 's';
  const categories = [...new Set(offenders.map((o) => o.category))];
  const catLabel =
    categories.length > 1
      ? `mixed ${categories.join(' + ')}`
      : categories[0] === 'activity'
        ? 'activity'
        : 'transport';
  return `Selling price below floor on ${offenders.length} ${catLabel} line${plural} — needs manager approval. Submit a change request instead. (${sample}${more})`;
}
