import { Injectable } from '@nestjs/common';
import * as ExcelJS from 'exceljs';
import { DriverAssignmentService } from './driver-assignment.service';
import { PermissionCheckService } from '../../../common/permissions/permission-check.service';
import { ModuleName, AccessLevel } from '../../../entities/role-permission.entity';

/**
 * Movement Dashboard Excel export.
 *
 * Builds a three-sheet workbook (Transport / Activity / Special) from
 * the same getMovementChart() payload the FE Chart + Planner consume.
 * Row fills flag assignment state at a glance:
 *   - Yellow  → assigned (driver+vehicle on transport, supplier on
 *               activity / special)
 *   - Red     → pending (missing driver/supplier)
 *
 * Actual-cost columns are gated by the floor-pricing view permission
 * so a Sales export doesn't leak margin data.
 */
@Injectable()
export class MovementChartExportService {
  constructor(
    private readonly driverAssignment: DriverAssignmentService,
    private readonly perms: PermissionCheckService,
  ) {}

  async buildWorkbook(
    destinationId: string | undefined,
    date: string,
    dateTo?: string,
  ): Promise<{ buffer: Buffer; filename: string }> {
    const rangeEnd = dateTo && dateTo >= date ? dateTo : undefined;
    const data = await this.driverAssignment.getMovementChart(destinationId, date, {
      date_to: rangeEnd,
    });
    const isRange = !!rangeEnd && rangeEnd !== date;

    const canSeeTransportCost = await this.perms.hasAtLeast(
      ModuleName.TRANSPORT_FLOOR_PRICING,
      AccessLevel.VIEW,
    );
    const canSeeActivityCost = await this.perms.hasAtLeast(
      ModuleName.ACTIVITY_FLOOR_PRICING,
      AccessLevel.VIEW,
    );

    const wb = new ExcelJS.Workbook();
    wb.creator = 'DMC';
    wb.created = new Date();

    // Row 1 header style + fills + status colours shared across sheets.
    const HEADER_FILL: ExcelJS.FillPattern = {
      type: 'pattern',
      pattern: 'solid',
      fgColor: { argb: 'FFE2E8F0' },
    };
    const ASSIGNED_FILL: ExcelJS.FillPattern = {
      type: 'pattern',
      pattern: 'solid',
      fgColor: { argb: 'FFFFF3B0' }, // amber-100
    };
    const PENDING_FILL: ExcelJS.FillPattern = {
      type: 'pattern',
      pattern: 'solid',
      fgColor: { argb: 'FFFECACA' }, // rose-200
    };

    // ── Transport sheet ───────────────────────────────────────────────
    const transportOps = data.by_booking.flatMap((b) =>
      b.ops.filter((op: { type: string }) => op.type === 'transport'),
    );
    const tSheet = wb.addWorksheet('Transport');
    const transportCols: Array<{ header: string; key: string; width: number }> = [];
    if (isRange) {
      transportCols.push({ header: 'Date', key: 'date', width: 12 });
    }
    transportCols.push(
      { header: 'Bill No.', key: 'booking_number', width: 12 },
      { header: 'Guest Name', key: 'guest_name', width: 22 },
      { header: 'Adult', key: 'adults', width: 8 },
      { header: 'Child', key: 'children', width: 8 },
      { header: 'Pickup Time', key: 'pickup_time', width: 12 },
      { header: 'RTN', key: 'return_time', width: 12 },
      { header: 'Pickup Location', key: 'pickup_address', width: 30 },
      { header: 'Drop To', key: 'dropoff_address', width: 30 },
      { header: 'Remarks', key: 'notes', width: 30 },
      { header: 'Supplier', key: 'supplier_name', width: 20 },
      { header: 'Driver', key: 'driver_name', width: 20 },
      { header: 'Vehicle Type', key: 'vehicle_type', width: 14 },
      { header: 'Selling Price', key: 'selling', width: 14 },
    );
    if (canSeeTransportCost) {
      transportCols.push({ header: 'Cost', key: 'cost', width: 14 });
    }
    transportCols.push(
      { header: 'Status', key: 'op_status', width: 12 },
      { header: 'Salesperson Name', key: 'salesperson_name', width: 20 },
      { header: 'Guest Number', key: 'guest_mobile', width: 16 },
      { header: 'Copy Messages', key: 'copy_messages', width: 50 },
    );
    tSheet.columns = transportCols;

    for (const op of transportOps) {
      const isAssigned =
        !!(op.driver_id || op.driver_name) && !!(op.supplier_id || op.supplier_name);
      // Multi-supplier flattening — the junction array carries every
      // assigned supplier for the row. Fall back to the legacy
      // supplier_name field for old data. Format as
      // "Name (role) [cost 150]; …" so ops can eyeball both roles
      // and per-supplier costs in one column.
      const suppliersList: Array<{ name: string; phone: string; role: string; cost: number | null }> =
        (op.suppliers && op.suppliers.length > 0)
          ? op.suppliers.map((s: { name: string; phone: string; role: string; actual_supplier_cost: number | null }) => ({
              name: s.name,
              phone: s.phone,
              role: s.role,
              cost: s.actual_supplier_cost,
            }))
          : op.supplier_name
            ? [{ name: op.supplier_name, phone: op.supplier_phone || '', role: 'primary', cost: null }]
            : [];
      const supplierNames = suppliersList
        .map((s) => (s.role && s.role !== 'primary' ? `${s.name} (${s.role})` : s.name))
        .join('; ');
      const row = tSheet.addRow({
        ...(isRange ? { date: op.date || date } : {}),
        booking_number: op.booking_number,
        guest_name: op.guest_name,
        adults: op.adults ?? 0,
        children: op.children ?? 0,
        pickup_time: to12h(op.time),
        return_time: to12h(op.return_time || op.dropoff_time),
        pickup_address: op.pickup_address || '',
        dropoff_address: op.dropoff_address || '',
        notes: op.notes || '',
        supplier_name: supplierNames,
        driver_name: op.driver_name || '',
        vehicle_type: op.vehicle_type_label || '',
        selling: Number(op.subtotal) || 0,
        ...(canSeeTransportCost
          ? { cost: op.actual_supplier_cost != null ? Number(op.actual_supplier_cost) : '' }
          : {}),
        op_status: (op.op_status || 'UNPLANNED').toUpperCase(),
        salesperson_name: op.salesperson_name || '',
        guest_mobile: op.guest_mobile || '',
        copy_messages: buildCopyMessages(op, false),
      });
      applyRowFill(row, isAssigned ? ASSIGNED_FILL : PENDING_FILL);
    }
    styleHeader(tSheet, HEADER_FILL);

    // ── Activity sheet ────────────────────────────────────────────────
    const aSheet = wb.addWorksheet('Activity');
    const activityOps = data.by_activity.flatMap((g) => g.items);
    const activityCols: Array<{ header: string; key: string; width: number }> = [
      { header: 'Date', key: 'date', width: 12 },
      { header: 'Booking #', key: 'booking_number', width: 12 },
      { header: 'Guest Name', key: 'guest_name', width: 22 },
      { header: 'Guest Mobile', key: 'guest_mobile', width: 16 },
      { header: 'Activity', key: 'activity_name', width: 24 },
      { header: 'Ticket / Slot', key: 'ticket', width: 20 },
      { header: 'Adults', key: 'adults', width: 8 },
      { header: 'Children', key: 'children', width: 8 },
      { header: 'Supplier', key: 'supplier_name', width: 20 },
      { header: 'Assigned Ticket #', key: 'ticket_ref', width: 16 },
      { header: 'Selling Price', key: 'selling', width: 14 },
    ];
    if (canSeeActivityCost) {
      activityCols.push({ header: 'Actual Cost', key: 'cost', width: 14 });
    }
    activityCols.push(
      { header: 'Op Status', key: 'op_status', width: 12 },
      { header: 'Remarks', key: 'notes', width: 30 },
    );
    aSheet.columns = activityCols;

    for (const op of activityOps) {
      const isAssigned = !!(op.supplier_id || op.supplier_name);
      const row = aSheet.addRow({
        // Row's own service date — in range mode `date` is only the
        // range start.
        date: op.date || date,
        booking_number: op.booking_number,
        guest_name: op.guest_name,
        guest_mobile: op.guest_mobile,
        activity_name: op.activity_name,
        ticket: [op.ticket_name, op.slot].filter(Boolean).join(' · '),
        adults: op.adults || 0,
        children: op.children || 0,
        supplier_name: flattenSuppliers(op) || op.supplier_name || '',
        ticket_ref: op.assigned_ticket_ref || '',
        selling: Number(op.subtotal) || 0,
        ...(canSeeActivityCost
          ? { cost: op.actual_supplier_cost != null ? Number(op.actual_supplier_cost) : '' }
          : {}),
        op_status: (op.op_status || 'UNPLANNED').toUpperCase(),
        notes: op.notes || '',
      });
      applyRowFill(row, isAssigned ? ASSIGNED_FILL : PENDING_FILL);
    }
    styleHeader(aSheet, HEADER_FILL);

    // ── Special sheet ─────────────────────────────────────────────────
    const sSheet = wb.addWorksheet('Special');
    const specialCols: Array<{ header: string; key: string; width: number }> = [
      { header: 'Date', key: 'date', width: 12 },
      { header: 'Booking #', key: 'booking_number', width: 12 },
      { header: 'Guest Name', key: 'guest_name', width: 22 },
      { header: 'Guest Mobile', key: 'guest_mobile', width: 16 },
      { header: 'Service Name', key: 'service_name', width: 28 },
      { header: 'Supplier', key: 'supplier_name', width: 20 },
      { header: 'Selling Price', key: 'selling', width: 14 },
    ];
    // Special items don't carry a floor/cost-pricing module of their
    // own; gate on transport floor as the closest permission surface.
    if (canSeeTransportCost) {
      specialCols.push({ header: 'Actual Cost', key: 'cost', width: 14 });
    }
    specialCols.push({ header: 'Remarks', key: 'notes', width: 30 });
    sSheet.columns = specialCols;

    for (const op of data.by_special) {
      const isAssigned = !!(op.supplier_id || op.supplier_name);
      const row = sSheet.addRow({
        date: op.date || date,
        booking_number: op.booking_number,
        guest_name: op.guest_name,
        guest_mobile: op.guest_mobile,
        service_name: op.service_name,
        supplier_name: flattenSuppliers(op) || op.supplier_name || '',
        selling: Number(op.total_price) || 0,
        ...(canSeeTransportCost
          ? { cost: op.actual_supplier_cost != null ? Number(op.actual_supplier_cost) : '' }
          : {}),
        notes: op.comments || '',
      });
      applyRowFill(row, isAssigned ? ASSIGNED_FILL : PENDING_FILL);
    }
    styleHeader(sSheet, HEADER_FILL);

    const buffer = (await wb.xlsx.writeBuffer()) as unknown as Buffer;
    // Destination-slug isn't easily available here (getMovementChart
    // returns none) — use a short "all" or dest id prefix. FE overrides
    // via the Content-Disposition anyway; keep the BE filename
    // conservative.
    const destPart = destinationId ? destinationId.slice(0, 8) : 'all';
    const datePart = isRange ? `${date}-to-${rangeEnd}` : date;
    const filename = `movement-dashboard-${destPart}-${datePart}.xlsx`;
    return { buffer, filename };
  }
}

// ── helpers ────────────────────────────────────────────────────────

const BODY_FONT_SIZE = 12;
const HEADER_FONT_SIZE = 13;
const BODY_ROW_HEIGHT = 26;
const HEADER_ROW_HEIGHT = 30;

function styleHeader(ws: ExcelJS.Worksheet, fill: ExcelJS.FillPattern) {
  const row = ws.getRow(1);
  row.font = { bold: true, size: HEADER_FONT_SIZE };
  row.fill = fill;
  row.alignment = { vertical: 'middle', wrapText: true };
  row.height = HEADER_ROW_HEIGHT;
  ws.views = [{ state: 'frozen', ySplit: 1 }];
  // Bump every data row's font + height too — set here so the caller
  // doesn't have to remember on each addRow() site.
  for (let i = 2; i <= ws.rowCount; i++) {
    const r = ws.getRow(i);
    r.font = { size: BODY_FONT_SIZE };
    r.alignment = { vertical: 'middle', wrapText: true };
    r.height = BODY_ROW_HEIGHT;
  }
}

function applyRowFill(row: ExcelJS.Row, fill: ExcelJS.FillPattern) {
  row.eachCell({ includeEmpty: true }, (cell) => {
    cell.fill = fill;
  });
}

function pad2(n: number): string {
  return String(n).padStart(2, '0');
}

/** Flatten multi-supplier junction rows into a display string used
 *  across the Activity + Special sheets. Empty when the op has no
 *  suppliers array (legacy shape). */
function flattenSuppliers(op: {
  suppliers?: Array<{ name: string; role: string }>;
}): string {
  if (!op.suppliers || op.suppliers.length === 0) return '';
  return op.suppliers
    .map((s) => (s.role && s.role !== 'primary' ? `${s.name} (${s.role})` : s.name))
    .join('; ');
}

function buildCopyMessages(
  op: {
    booking_number?: string;
    guest_name?: string;
    guest_mobile?: string;
    time?: string | null;
    return_time?: string | null;
    from_city?: string;
    to_city?: string;
    pickup_address?: string | null;
    dropoff_address?: string | null;
    supplier_name?: string | null;
    driver_name?: string | null;
    adults?: number;
    children?: number;
    notes?: string | null;
    activity_name?: string;
    ticket_name?: string | null;
    slot?: string | null;
  },
  isActivity: boolean,
): string {
  const guest = (op.guest_name || '').split(/\s+/)[0] || 'there';
  const customer = isActivity
    ? [
        `Hi ${guest}, here's your activity update:`,
        op.activity_name ? `🎯 ${op.activity_name}` : '',
        op.ticket_name ? `Ticket: ${op.ticket_name}` : '',
        op.slot ? `Slot: ${op.slot}` : op.time ? `Time: ${to12h(op.time)}` : '',
        `Booking: ${op.booking_number || ''}`,
        op.notes ? `📝 ${op.notes}` : '',
      ]
    : [
        `Hi ${guest}, here's your transfer update:`,
        op.from_city && op.to_city ? `🚗 ${op.from_city} → ${op.to_city}` : '🚗 Transfer',
        op.time ? `⏰ Pickup: ${to12h(op.time)}` : '',
        op.pickup_address ? `📍 From: ${op.pickup_address}` : '',
        op.dropoff_address ? `🏁 To: ${op.dropoff_address}` : '',
        `Booking: ${op.booking_number || ''}`,
        op.notes ? `📝 ${op.notes}` : '',
      ];
  const contact = op.driver_name || op.supplier_name || 'Supplier';
  const supplier = isActivity
    ? [
        `Hi ${contact}, activity request:`,
        `Booking: ${op.booking_number || ''} · Guest: ${op.guest_name || ''}`,
        op.activity_name ? `Activity: ${op.activity_name}` : '',
        `Pax: ${op.adults || 0}A + ${op.children || 0}C`,
      ]
    : [
        `Hi ${contact}, pickup request:`,
        `Booking: ${op.booking_number || ''} · Guest: ${op.guest_name || ''}`,
        op.time ? `Time: ${to12h(op.time)}` : '',
        op.pickup_address ? `Pickup: ${op.pickup_address}` : '',
        op.dropoff_address ? `Drop: ${op.dropoff_address}` : '',
        `Pax: ${op.adults || 0}A + ${op.children || 0}C`,
      ];
  return [...customer, '', ...supplier].filter((line, i, arr) => line !== '' || arr[i - 1] !== '').join('\n');
}

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 `${pad2(h)}:${mm} ${period}`;
}
