import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ClsService } from 'nestjs-cls';
import { BookingEntity } from '../../../entities/booking.entity';
import { BookingHotelItemEntity } from '../../../entities/booking-hotel-item.entity';
import { TenantSettingsEntity } from '../../../entities/tenant-settings.entity';
import { UserEntity } from '../../../entities/user.entity';
import { DestinationEntity } from '../../../entities/destination.entity';
import {
  pickPrimaryPeriod,
  sumTotalRooms,
} from '../../../common/hotel/hotel-primary-period';

/**
 * Builds the data bag for EMAIL/BOOKING and WHATSAPP/BOOKING
 * templates.
 *
 * Mirrors QueryContextResolver's shape — same field names, same
 * precomputed strings — but sourced from the Booking entity + its
 * items instead of the parent Query's selected quote. The two
 * default templates (Standard Confirmation email + Standard
 * Confirmation WhatsApp) reuse the same tokens, so switching a
 * template from EMAIL/QUERY to EMAIL/BOOKING is a matter of picking
 * the right context — no rewrite needed.
 *
 * `guest_name`, `guest_mobile`, `start_date`, `end_date`, `adults`
 * etc. live on the parent query (see query.entity), so the resolver
 * loads booking with `query: true` relation.
 */
@Injectable()
export class BookingContextResolver {
  constructor(
    @InjectRepository(BookingEntity) private readonly bookings: Repository<BookingEntity>,
    @InjectRepository(TenantSettingsEntity)
    private readonly tenantSettings: Repository<TenantSettingsEntity>,
    @InjectRepository(UserEntity) private readonly users: Repository<UserEntity>,
    @InjectRepository(DestinationEntity)
    private readonly destinations: Repository<DestinationEntity>,
    private readonly cls: ClsService,
  ) {}

  async build(bookingId: string): Promise<Record<string, unknown>> {
    const tenantId = this.cls.get<string>('tenant_id');
    const booking = await this.bookings.findOne({
      where: { id: bookingId, tenant_id: tenantId, is_deleted: false } as any,
      relations: {
        // Destination lives on booking.query, not booking directly —
        // same TypeORM shape the MD resolvers use. The line-item
        // sub-relations still walk their own destination via the
        // hotel master (booking_hotel_items.hotel.destination).
        query: { destination: true, source: true, currency: true },
        selling_currency: true,
        hotel_items: { hotel: { destination: true } },
        transport_items: { transport_service: true, vehicle_type: true },
        activity_items: { activity: true, ticket: true },
        special_items: true,
      } as any,
    });
    if (!booking) throw new NotFoundException('Booking not found');

    const settings = await this.tenantSettings.findOne({
      where: { tenant_id: tenantId } as any,
    });

    const q = (booking as any).query ?? {};
    const startDate: string | null = q.start_date ?? null;
    const endDate: string | null = q.end_date ?? null;
    const totalNights = computeNights(startDate, endDate);
    const totalDays = totalNights + 1;
    const childrenCount = Array.isArray(q.children_ages) ? q.children_ages.length : 0;

    const salesUserId =
      (Array.isArray(q.sales_team) && q.sales_team[0]) || booking.created_by || q.created_by;
    const salesUser = salesUserId
      ? await this.users.findOne({
          where: { id: salesUserId, tenant_id: tenantId } as any,
          select: { id: true, name: true, phone: true } as any,
        })
      : null;

    const destinationId = q.destination_id || (booking as any).destination_id;
    const taxLabel = destinationId ? await this.resolveTaxLabel(destinationId) : '';

    const pax = paxSummary(q.adults ?? 0, childrenCount, q.infants ?? 0);
    const priceCurrency =
      booking.selling_currency?.code ?? q.currency?.code ?? '';
    const priceValue = Number(
      booking.selling_total || (booking as any).grand_total || 0,
    );
    const priceAmount = priceCurrency
      ? `${priceCurrency} ${fmtMoney(priceValue)}`
      : fmtMoney(priceValue);

    const dayBlocks = buildDayBlocks(booking, startDate, totalDays);
    const specialsBlock = buildSpecialsBlock(
      (booking as any).special_items || [],
    );
    const hasSpecials = specialsBlock.length > 0;

    let signatureBlock = '';
    if (salesUser?.name || settings?.company_name) {
      const parts: string[] = [];
      if (salesUser?.name && settings?.company_name) {
        parts.push(`— ${salesUser.name}, ${settings.company_name}`);
      } else if (settings?.company_name) {
        parts.push(`— ${settings.company_name}`);
      } else if (salesUser?.name) {
        parts.push(`— ${salesUser.name}`);
      }
      if ((salesUser as any)?.phone)
        parts.push(`📞 ${(salesUser as any).phone}`);
      signatureBlock = parts.join('\n');
    }

    return {
      divider: '━━━━━━━━━━━━━━━━━━',
      title_emoji: '🌟',
      company: {
        name: settings?.company_name ?? '',
        email: settings?.email ?? '',
        phone: settings?.phone ?? '',
        address: settings?.address ?? '',
        logo_url: settings?.logo_url ?? '',
      },
      booking: {
        id: booking.id,
        number: booking.booking_number,
        status: booking.status,
        payment_status: booking.payment_status,
      },
      query: {
        id: q.id ?? '',
        number: q.query_number ?? '',
        reference_id: booking.booking_number,
        guest_name: q.guest_name ?? '',
        greeting_name: q.guest_name || 'there',
        guest_mobile: q.guest_mobile ?? '',
        guest_email: q.guest_email ?? '',
        pax: {
          adults: q.adults ?? 0,
          children: childrenCount,
          infants: q.infants ?? 0,
        },
        pax_summary: pax,
        start_date: formatLongDate(startDate),
        end_date: formatLongDate(endDate),
        date_range: `${formatShortDate(startDate)} — ${formatShortDate(endDate)} ${yearOf(endDate)}`,
        days: totalDays,
        nights: totalNights,
        duration: `${totalDays} Days / ${totalNights} Nights`,
        source_name: q.source?.name ?? '',
      },
      destination: {
        name: q.destination?.name ?? (booking as any).destination?.name ?? '',
      },
      quote: {
        // Booking is post-quote — surface the same fields the QUERY
        // templates read so a template can render either context
        // without a rewrite.
        selling_total: fmtMoney(priceValue),
        grand_total: fmtMoney((booking as any).grand_total ?? priceValue),
        currency: priceCurrency,
        customer_remarks: (booking as any).customer_remarks ?? '',
      },
      customer_remarks: (booking as any).customer_remarks ?? '',
      price_amount: priceAmount,
      tax_label: taxLabel || 'taxes',
      day_blocks: dayBlocks,
      has_specials: hasSpecials,
      specials_block: specialsBlock,
      signature_block: signatureBlock,
      hotels: flatHotels(booking.hotel_items || []),
      inclusion_by_day: buildInclusionByDay(booking, startDate),
      terms: (settings as any)?.terms_and_conditions ?? '',
    };
  }

  private async resolveTaxLabel(destinationId: string): Promise<string> {
    const rows = (await this.destinations.manager.query(
      `SELECT tt.name
         FROM destinations d
         LEFT JOIN tax_slabs ts ON ts.id = d.tax_slab_id AND ts.is_deleted = false
         LEFT JOIN tax_types tt ON tt.id = ts.tax_type_id AND tt.is_deleted = false
        WHERE d.id = $1
        LIMIT 1`,
      [destinationId],
    )) as Array<{ name: string | null }>;
    return rows[0]?.name?.trim() || '';
  }
}

// ── Shared helpers (parallel to QueryContextResolver's copy) ──────

function paxSummary(adults: number, children: number, infants: number): string {
  const parts: string[] = [];
  parts.push(`${adults} ${adults === 1 ? 'Adult' : 'Adults'}`);
  if (children > 0) parts.push(`${children} ${children === 1 ? 'Child' : 'Children'}`);
  if (infants > 0) parts.push(`${infants} ${infants === 1 ? 'Infant' : 'Infants'}`);
  return parts.join(', ');
}

function buildDayBlocks(
  booking: BookingEntity,
  startDate: string | null,
  totalDays: number,
): string {
  if (!startDate) return '';
  const lines: string[] = [];
  const transports = (booking as any).transport_items || [];
  const activities = (booking as any).activity_items || [];
  const hotels = booking.hotel_items || [];
  const specialsWithDate = ((booking as any).special_items || []).filter(
    (s: any) => !!s.date,
  );

  const hotelFirstNight = new Map<string, number>();
  for (const h of hotels as any[]) {
    if (Array.isArray(h.selected_nights) && h.selected_nights.length > 0) {
      hotelFirstNight.set(h.id, Math.min(...h.selected_nights));
    }
  }

  for (let i = 0; i < totalDays; i++) {
    const date = addDaysISO(startDate, i);
    const hotel = hotels.find(
      (h: any) => Array.isArray(h.selected_nights) && h.selected_nights.includes(i),
    );
    const dayTransports = transports.filter((t: any) => t.date === date);
    const dayActivities = activities.filter((a: any) => a.date === date);
    const daySpecials = specialsWithDate.filter((s: any) => s.date === date);
    const empty =
      !hotel &&
      dayTransports.length === 0 &&
      dayActivities.length === 0 &&
      daySpecials.length === 0;
    if (empty) continue;

    lines.push(`*Day ${i + 1} · ${formatShortDate(date)} (${weekday(date)})*`);
    if (hotel) {
      const primary = pickPrimaryPeriod((hotel as any).periods);
      const rp: string[] = [(hotel as any).hotel?.name || 'Hotel'];
      if (primary.room_type) rp.push(primary.room_type);
      if (primary.meal_plan) rp.push(primary.meal_plan);
      lines.push(`🏨 ${rp.join(' · ')}`);
      if (
        hotelFirstNight.get((hotel as any).id) === i &&
        (hotel as any).notes
      ) {
        lines.push(`   📝 ${(hotel as any).notes}`);
      }
    }
    for (const t of dayTransports) lines.push(describeTransport(t));
    for (const a of dayActivities) lines.push(describeActivity(a));
    for (const s of daySpecials) lines.push(describeSpecial(s));
    lines.push('');
  }
  while (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();
  return lines.join('\n');
}

function describeTransport(t: any): string {
  const ts = t.transport_service;
  const vt = t.vehicle_type;
  const route = ts ? `${ts.from_city || ''} → ${ts.to_city || ''}` : 'Transport';
  const mode = t.is_shared ? 'SIC' : 'PVT';
  const parts: string[] = [route];
  if (vt?.name) parts.push(`· ${vt.name}`);
  parts.push(`· ${mode}`);
  const lines: string[] = [`🚗 ${parts.join(' ')}`];
  const pickup = to12h(t.pickup_time);
  const dropoff = to12h(t.dropoff_time);
  if (pickup && dropoff) lines.push(`   ⏰ ${pickup} → ${dropoff}`);
  else if (pickup) lines.push(`   ⏰ Pickup ${pickup}`);
  else if (dropoff) lines.push(`   ⏰ Drop ${dropoff}`);
  if (t.pickup_address) lines.push(`   📍 ${t.pickup_address}`);
  if (t.dropoff_address) lines.push(`   🏁 ${t.dropoff_address}`);
  if (t.notes) lines.push(`   📝 ${t.notes}`);
  return lines.join('\n');
}

function describeActivity(a: any): string {
  const name = a.activity?.name || 'Activity';
  const ticket = a.ticket?.name ? ` — ${a.ticket.name}` : '';
  const q = Number(a.quantity) || 0;
  const paxBits: string[] = [];
  if (a.age_group === 'adult' && q > 0) paxBits.push(`${q}A`);
  else if (a.age_group === 'child' && q > 0) paxBits.push(`${q}C`);
  else if (a.age_group === 'senior' && q > 0) paxBits.push(`${q}S`);
  const pax = paxBits.length > 0 ? ` · ${paxBits.join(' ')}` : '';
  const head = `🎯 ${name}${ticket}${pax}`;
  const lines = [head];
  if (a.slot) lines.push(`   🕒 Slot ${a.slot}`);
  if (a.notes) lines.push(`   📝 ${a.notes}`);
  return lines.join('\n');
}

function describeSpecial(s: any): string {
  const head = `✨ ${s.service_name || 'Service'}`;
  const lines = [head];
  if (s.comments) lines.push(`   📝 ${s.comments}`);
  return lines.join('\n');
}

function buildSpecialsBlock(items: any[]): string {
  const undated = items.filter((s) => !s.date);
  if (undated.length === 0) return '';
  const lines = ['*✨ Additional Services*'];
  for (const s of undated) lines.push(`• ${s.service_name || 'Service'}`);
  return lines.join('\n');
}

function flatHotels(items: BookingHotelItemEntity[]) {
  return items.map((h) => {
    // Room/meal/rooms/pax now live on child periods — expose the
    // primary period so existing template variables continue to work.
    const primary = pickPrimaryPeriod((h as any).periods);
    const totalRooms = sumTotalRooms((h as any).periods);
    return {
      hotel_name: (h as any).hotel?.name ?? '',
      city: (h as any).hotel?.destination?.name ?? '',
      star_rating: (h as any).hotel?.star_rating ?? '',
      room_type: primary.room_type,
      meal_plan: primary.meal_plan,
      check_in_date: formatLongDate((h as any).check_in_date),
      check_out_date: formatLongDate((h as any).check_out_date),
      nights: (h as any).nights,
      num_rooms: totalRooms,
      pax_per_room: primary.pax_per_room,
    };
  });
}

interface DayGroup {
  day_number: number;
  date: string;
  weekday: string;
  items: { label: string }[];
}
function buildInclusionByDay(booking: BookingEntity, startDate: string | null): DayGroup[] {
  if (!startDate) return [];
  const groups = new Map<string, DayGroup>();
  const add = (date: string | null, label: string) => {
    if (!date) return;
    if (!groups.has(date)) {
      groups.set(date, {
        day_number: dayNumberOf(startDate, date),
        date: formatLongDate(date),
        weekday: weekday(date),
        items: [],
      });
    }
    groups.get(date)!.items.push({ label });
  };
  for (const t of ((booking as any).transport_items || []) as any[]) {
    const ts = t.transport_service;
    const vt = t.vehicle_type;
    const route = ts ? `${ts.from_city} → ${ts.to_city}` : 'Transport';
    add(
      t.date,
      `${route}${vt?.name ? ' — ' + vt.name : ''}${t.category ? ' (' + t.category + ')' : ''}`,
    );
  }
  for (const a of ((booking as any).activity_items || []) as any[]) {
    const label = a.activity?.name ?? 'Activity';
    const tkPart = a.ticket?.name ? ` — ${a.ticket.name}` : '';
    add(a.date, `${label}${tkPart}${a.quantity ? ' × ' + a.quantity : ''}`);
  }
  for (const s of ((booking as any).special_items || []) as any[]) {
    add(s.date, s.service_name || 'Special');
  }
  return Array.from(groups.values()).sort((a, b) => a.day_number - b.day_number);
}

function computeNights(startDate: string | null, endDate: string | null): number {
  if (!startDate || !endDate) return 0;
  const s = new Date(startDate + 'T00:00:00Z').getTime();
  const e = new Date(endDate + 'T00:00:00Z').getTime();
  return Math.max(0, Math.round((e - s) / 86400000));
}
function dayNumberOf(startDate: string, date: string): number {
  return computeNights(startDate, date) + 1;
}
function addDaysISO(startDate: string, offset: number): string {
  const d = new Date(startDate + 'T00:00:00Z');
  d.setUTCDate(d.getUTCDate() + offset);
  return d.toISOString().slice(0, 10);
}
function formatLongDate(d: string | null | undefined): string {
  if (!d) return '';
  const [y, m, day] = d.split('T')[0].split('-');
  const months = [
    'January','February','March','April','May','June',
    'July','August','September','October','November','December',
  ];
  return `${parseInt(day)} ${months[parseInt(m) - 1] ?? m}, ${y}`;
}
function formatShortDate(d: string | null | undefined): string {
  if (!d) return '';
  const [, m, day] = d.split('T')[0].split('-');
  const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  return `${parseInt(day)} ${months[parseInt(m) - 1] ?? m}`;
}
function yearOf(d: string | null | undefined): string {
  if (!d) return '';
  return d.split('T')[0].split('-')[0];
}
function weekday(d: string): string {
  const dt = new Date(d + 'T00:00:00Z');
  const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
  return days[dt.getUTCDay()];
}
function to12h(hhmm: string | null | undefined): string {
  if (!hhmm) return '';
  const m = /^(\d{1,2}):(\d{2})/.exec(hhmm);
  if (!m) return hhmm;
  let h = Number(m[1]);
  const mm = m[2];
  const period = h >= 12 ? 'PM' : 'AM';
  h = h % 12 || 12;
  return `${h}:${mm} ${period}`;
}
function fmtMoney(n: unknown): string {
  const v = Number(n);
  if (!Number.isFinite(v)) return '0';
  return v.toLocaleString('en-US', {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  });
}
