/**
 * Floor-price guard — finds activity lines whose unit_price falls
 * below the configured floor for the active season. Mirrors the
 * transport variant in modules/transport-pricing/floor-price-guard.ts
 * — same algorithm, different identity tuple.
 *
 * Lookup is keyed by (tenant, activity_id, ticket_id|null, age_group,
 * currency_id) — the same identity activity_pricing's unique index
 * uses. Currency is resolved via the activity's destination FK.
 *
 * Missing floor config → line is skipped (NULL floor = "no floor for
 * that season"). Missing date → line is skipped (no season to resolve).
 *
 * The shared `BelowFloorOffender` carries a `category` discriminator
 * so QuoteService / BookingService can merge transport + activity
 * offenders into a single Change Request payload — single CR per
 * save regardless of how many categories are below floor.
 */

import { type EntityManager } from 'typeorm';
import { type BelowFloorOffender } from '../transport-pricing/floor-price-guard';

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

interface ActivityItemLine {
  activity_id?: string | null;
  ticket_id?: string | null;
  age_group?: string | null;
  date?: string | null;
  unit_price?: number | string | null;
}

interface PricingRow {
  activity_id: string;
  ticket_id: string | null;
  age_group: string;
  destination_id: string;
  currency_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;
}

export async function findBelowFloorActivityLines(
  manager: EntityManager,
  tenantId: string,
  activityItems: ActivityItemLine[] | null | undefined,
): Promise<BelowFloorOffender[]> {
  type ValidLine = ActivityItemLine & {
    activity_id: string;
    date: string;
    age_group: string;
    unit_price: number | string;
  };
  const lines = (activityItems || []).filter((l): l is ValidLine => {
    if (!l?.activity_id) return false;
    if (!l?.date) return false;
    if (!(Number(l?.unit_price) > 0)) return false;
    if (!l?.age_group) return false;
    return true;
  });
  if (lines.length === 0) return [];

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

  // Pull every (activity × ticket × age × currency) pricing row for
  // the activities involved. Indexed below for O(1) lookup per line.
  const pricingRows: PricingRow[] = await manager.query(
    `SELECT ap.activity_id, ap.ticket_id, ap.age_group, ap.destination_id, ap.currency_id,
            ap.floor_default_price,
            ap.floor_season_1_price, ap.floor_season_2_price, ap.floor_season_3_price,
            ap.floor_season_4_price, ap.floor_season_5_price
     FROM activity_pricing ap
     WHERE ap.tenant_id = $1
       AND ap.is_deleted = false
       AND ap.activity_id = ANY($2::uuid[])`,
    [tenantId, activityIds],
  );

  // Index pricing by (activity, ticket_id_or_empty, age_group). Ticket
  // is nullable on pricing — use '' as the key for those rows so a
  // line with no ticket still finds its match. Currency is implicit
  // (one currency per destination).
  const pricingByKey = new Map<string, PricingRow>();
  for (const r of pricingRows) {
    const ticketKey = r.ticket_id ?? '';
    pricingByKey.set(`${r.activity_id}|${ticketKey}|${r.age_group}`, r);
  }

  // Load seasons per destination once. Multiple lines may share a
  // destination so 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 ticketKey = l.ticket_id ?? '';
    const pricing = pricingByKey.get(`${l.activity_id}|${ticketKey}|${l.age_group}`);
    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) {
      offenders.push({
        category: 'activity',
        label: `Activity ${String(l.activity_id).slice(0, 6)}… ${l.age_group} (${l.date})`,
        selling,
        floor,
        season_name: seasonName,
        // Reuse the transport offender shape — these fields aren't
        // semantically meaningful for activity but keep the union
        // type honest. Approval UI should branch on `category`.
        transport_service_id: l.activity_id,
        vehicle_type_id: l.ticket_id ?? null,
        mode: 'PVT',
      });
    }
  }

  return offenders;
}
