import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ClsService } from 'nestjs-cls';
import { QueryEntity } from '../../../entities/query.entity';
import { QueryQuoteEntity } from '../../../entities/query-quote.entity';
import { QueryQuoteHotelEntity } from '../../../entities/query-quote-hotel.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/QUERY and WHATSAPP/QUERY templates.
 *
 * The default WHATSAPP/QUERY seed template mirrors the output of
 * `generateItineraryMessage` in `frontend/src/lib/whatsapp-itinerary.ts`
 * character-for-character so the switch to template-driven Copy
 * Content on the Query surface produces the same wording as the
 * legacy hardcoded builder. To keep the mustache-lite template
 * readable, the algorithmic pieces (per-day hotel + item block,
 * specials block, signature) are precomputed as strings here; the
 * framing (greeting, price line, CTA, tenant sig) stays as
 * individually editable tokens.
 *
 * If ops rewrite the template to change wording, only the framing
 * tokens change. The `day_blocks` string can be rearranged around
 * headings but its internal per-line structure lives in this
 * resolver — same limitation applies to the current hardcoded
 * builder.
 */
@Injectable()
export class QueryContextResolver {
  constructor(
    @InjectRepository(QueryEntity) private readonly queries: Repository<QueryEntity>,
    @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(queryId: string): Promise<Record<string, unknown>> {
    const tenantId = this.cls.get<string>('tenant_id');
    const query = await this.queries.findOne({
      where: { id: queryId, tenant_id: tenantId, is_deleted: false } as any,
      relations: {
        destination: true,
        source: true,
        currency: true,
        quotes: {
          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 (!query) throw new NotFoundException('Query not found');

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

    const quote = pickSelectedQuote(query.quotes || []);
    const totalNights = computeNights(query.start_date, query.end_date);
    const totalDays = totalNights + 1;
    const childrenCount = (query.children_ages || []).length;

    // Sales contact — first user id in sales_team[] wins. Fallback to
    // the creator of the query so the signature isn't empty when no
    // team is assigned. Silently drop when neither resolves.
    const salesUserId =
      (Array.isArray(query.sales_team) && query.sales_team[0]) || query.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;

    // Tax label — first tax_slab on the destination (skip zero-rate
    // rows). Names joined by ' + ' if a slab has multiple components.
    const taxLabel = await this.resolveTaxLabel(query.destination_id);

    const pax = paxSummary(query.adults, childrenCount, query.infants);
    const priceCurrency = query.currency?.code ?? '';
    const priceAmount = quote ? `${priceCurrency} ${fmtMoney(quote.selling_total)}` : '';

    // Precompute the day-by-day block that reproduces the hardcoded
    // generateItineraryMessage output exactly. The template just
    // interpolates this as a single {{day_blocks}} token.
    const dayBlocks = quote ? buildDayBlocks(quote, query.start_date, totalDays) : '';
    const specialsBlock = quote ? buildSpecialsBlock(quote.special_items || []) : '';
    const hasSpecials = specialsBlock.length > 0;

    // Precomputed signature — the tail "— Name, Company\n📞 phone" is
    // easier to author here than through nested {{#if}} in the seed.
    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 {
      // ── Meta ─────────────────────────────────────────
      divider: '━━━━━━━━━━━━━━━━━━',
      title_emoji: '🌟',
      // ── Company (tenant settings) ────────────────────
      company: {
        name: settings?.company_name ?? '',
        email: settings?.email ?? '',
        phone: settings?.phone ?? '',
        address: settings?.address ?? '',
        logo_url: settings?.logo_url ?? '',
      },
      // ── Query ────────────────────────────────────────
      query: {
        id: query.id,
        number: query.query_number,
        reference_id: query.query_number,
        guest_name: query.guest_name ?? '',
        greeting_name: query.guest_name || 'there',
        guest_mobile: query.guest_mobile ?? '',
        guest_email: query.guest_email ?? '',
        pax: {
          adults: query.adults ?? 0,
          children: childrenCount,
          infants: query.infants ?? 0,
        },
        pax_summary: pax,
        start_date: formatLongDate(query.start_date),
        end_date: formatLongDate(query.end_date),
        date_range: `${formatShortDate(query.start_date)} — ${formatShortDate(query.end_date)} ${yearOf(query.end_date)}`,
        days: totalDays,
        nights: totalNights,
        duration: `${totalDays} Days / ${totalNights} Nights`,
        source_name: query.source?.name ?? '',
      },
      // ── Destination ──────────────────────────────────
      destination: {
        name: query.destination?.name ?? '',
      },
      // ── Selected quote ───────────────────────────────
      quote: quote
        ? {
            id: quote.id,
            option_number: (quote as any).option_number ?? '',
            grand_total: fmtMoney(quote.grand_total),
            selling_total: fmtMoney(quote.selling_total),
            currency: priceCurrency,
            customer_remarks: quote.customer_remarks ?? '',
          }
        : null,
      customer_remarks: quote?.customer_remarks ?? '',
      // ── Price / tax ──────────────────────────────────
      price_amount: priceAmount,
      tax_label: taxLabel || 'taxes',
      // ── Precomputed blocks ───────────────────────────
      day_blocks: dayBlocks,
      has_specials: hasSpecials,
      specials_block: specialsBlock,
      // ── Signature ────────────────────────────────────
      signature_block: signatureBlock,
      // ── Hotels — flat list for EMAIL/QUERY table ──────
      hotels: quote ? flatHotels(quote.hotel_items || []) : [],
      // ── Inclusion by day — richer list for EMAIL/QUERY ──
      inclusion_by_day: quote ? buildInclusionByDay(quote, query.start_date) : [],
      // ── Terms (from tenant settings) ─────────────────
      terms: (settings as any)?.terms_and_conditions ?? '',
    };
  }

  private async resolveTaxLabel(destinationId: string): Promise<string> {
    if (!destinationId) return '';
    // destinations.tax_slab_id is a plain uuid column (no TypeORM
    // relation object), so a raw manager query is the cleanest way
    // to reach the tax_type name in one hop.
    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() || '';
  }
}

// ── Selected-quote heuristic ──────────────────────────────────────
function pickSelectedQuote(quotes: QueryQuoteEntity[]): QueryQuoteEntity | null {
  const active = quotes.filter((q) => !q.is_deleted);
  if (active.length === 0) return null;
  const selected = active.find((q) => q.is_selected);
  if (selected) return selected;
  return active.slice().sort((a, b) => {
    const ta = new Date(a.updated_at || a.created_at).getTime();
    const tb = new Date(b.updated_at || b.created_at).getTime();
    return tb - ta;
  })[0];
}

// ── Pax summary ────────────────────────────────────────────────────
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(', ');
}

// ── Per-day WhatsApp block ─────────────────────────────────────────
// Reproduces the algorithm in generateItineraryMessage: skip empty
// days, print `*Day N · date (Weekday)*`, render hotel line + notes
// on first night, then per-item multi-line describe blocks.
function buildDayBlocks(
  quote: QueryQuoteEntity,
  startDate: string | null,
  totalDays: number,
): string {
  if (!startDate) return '';
  const lines: string[] = [];
  const transports = quote.transport_items || [];
  const activities = quote.activity_items || [];
  const hotels = quote.hotel_items || [];
  const specialsWithDate = (quote.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) {
      // Room/meal moved to child periods — surface the primary period's
      // labels so the day-block still reads as "Hotel · Deluxe · BB".
      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('');
  }

  // Trim the trailing blank line so the divider that follows the
  // block in the template doesn't get double-spaced.
  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');
}

// ── Standalone specials block (undated specials) ──────────────────
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');
}

// ── Flat hotel list for EMAIL/QUERY ────────────────────────────────
function flatHotels(items: QueryQuoteHotelEntity[]) {
  return items.map((h) => {
    // Template shape kept flat for backwards compatibility with
    // existing WhatsApp / email variables. Room + meal + rooms + pax
    // moved into child periods — collapse to a single-stay-primary
    // view here so `{{hotel.room_type}}` continues 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.check_in_date),
      check_out_date: formatLongDate(h.check_out_date),
      nights: h.nights,
      num_rooms: totalRooms,
      pax_per_room: primary.pax_per_room,
    };
  });
}

// ── inclusion_by_day for EMAIL/QUERY ───────────────────────────────
interface DayGroup {
  day_number: number;
  date: string;
  weekday: string;
  items: { label: string }[];
}
function buildInclusionByDay(quote: QueryQuoteEntity, 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 (quote.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 (quote.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 (quote.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);
}

// ── Date + time helpers ────────────────────────────────────────────
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,
  });
}
