import type { UserTicket, Event } from '@/lib/api';
import { formatPrice, parseDate, EVENT_TIMEZONE } from '@/lib/utils';

// Hours a booking holds a seat before the spot is released. There is no
// server-side enforcement field, so the countdown is derived from when the
// ticket was created, capped at the event start time.
export const PAYMENT_HOLD_HOURS = 24;

// Hours a pending-approval booking (payment already marked as sent) can wait
// for admin review before the auto-hold sweep releases the spot. Mirrors the
// backend's HOLD_THRESHOLD_HOURS env default - keep these in sync.
export const HOLD_THRESHOLD_HOURS = 72;

/** True once a booking has been auto-released after the approval hold window. */
export function isOnHold(ticket: Pick<UserTicket, 'status'> & { payment?: { status?: string } | null }): boolean {
  return ticket.status === 'on_hold' || ticket.payment?.status === 'on_hold';
}

/** Currency is Guarani with no decimals, e.g. "21 PYG". */
export function pyg(amount: number, currency: string = 'PYG'): string {
  return formatPrice(Number(amount) || 0, currency || 'PYG');
}

export function isManualProvider(provider?: string): boolean {
  return provider === 'bank_transfer' || provider === 'tpago';
}

/** A ticket is unpaid when its payment is still pending (not approved/paid). */
export function isUnpaid(ticket: Pick<UserTicket, 'status'> & { payment?: { status?: string } | null }): boolean {
  const ps = ticket.payment?.status;
  return ticket.status !== 'cancelled' && (ps === 'pending' || ps === 'failed');
}

export function isAwaitingApproval(ticket: { payment?: { status?: string } | null }): boolean {
  return ticket.payment?.status === 'pending_approval';
}

/**
 * Whether an attention banner (e.g. "your spot was released", "payment pending")
 * is still worth showing to the user. It only makes sense to nudge the user when
 * the event still exists, is still bookable (published/unlisted), and has not
 * already ended. This suppresses stale banners for deleted, unpublished,
 * cancelled/completed/archived, or past events.
 */
export function isActionableAttention(ticket: UserTicket): boolean {
  const event = ticket.event;
  if (!event) return false;
  if (event.status !== 'published' && event.status !== 'unlisted') return false;
  const refDate = event.endDatetime || event.startDatetime;
  if (!refDate) return false;
  return parseDate(refDate).getTime() > Date.now();
}

/**
 * The moment the seat hold expires. null when paid/awaiting/cancelled or when
 * the data needed to compute it is missing.
 */
export function holdExpiry(ticket: UserTicket): Date | null {
  if (!isUnpaid(ticket)) return null;
  const createdStr = ticket.payment?.createdAt || ticket.createdAt;
  if (!createdStr) return null;
  const created = parseDate(createdStr);
  let expiry = new Date(created.getTime() + PAYMENT_HOLD_HOURS * 60 * 60 * 1000);
  // Never hold past the event itself.
  const eventStart = ticket.event?.startDatetime ? parseDate(ticket.event.startDatetime) : null;
  if (eventStart && eventStart < expiry) expiry = eventStart;
  return expiry;
}

/** Amount owed for a ticket (payment amount preferred, falls back to event price). */
export function ticketAmount(ticket: UserTicket): { amount: number; currency: string } {
  const amount = ticket.payment?.amount ?? ticket.event?.price ?? 0;
  const currency = ticket.payment?.currency ?? ticket.event?.currency ?? 'PYG';
  return { amount: Number(amount) || 0, currency };
}

export interface BookingGroup {
  bookingId: string;
  tickets: UserTicket[];
}

/**
 * Group tickets that belong to the same booking (multi-ticket / guest tickets).
 * Tickets without a bookingId become single-ticket groups keyed by their id.
 * Order of first appearance is preserved.
 */
export function groupByBooking(tickets: UserTicket[]): BookingGroup[] {
  const groups = new Map<string, UserTicket[]>();
  const order: string[] = [];
  for (const ticket of tickets) {
    const key = ticket.bookingId || ticket.id;
    if (!groups.has(key)) {
      groups.set(key, []);
      order.push(key);
    }
    groups.get(key)!.push(ticket);
  }
  return order.map((bookingId) => ({ bookingId, tickets: groups.get(bookingId)! }));
}

/** Public URL embedded in the scannable QR (matches the PDF + scanner contract). */
export function ticketScanUrl(ticketId: string): string {
  const origin = typeof window !== 'undefined' ? window.location.origin : '';
  return `${origin}/ticket/${ticketId}`;
}

/** Download URL for a ticket / whole booking PDF. */
export function ticketPdfUrl(ticket: UserTicket): string {
  return ticket.bookingId
    ? `/api/tickets/booking/${ticket.bookingId}/pdf`
    : `/api/tickets/${ticket.id}/pdf`;
}

/** Build a Google Calendar "add event" URL — no backend needed. */
export function googleCalendarUrl(event: Event, locale: string): string {
  const start = parseDate(event.startDatetime);
  const end = event.endDatetime
    ? parseDate(event.endDatetime)
    : new Date(start.getTime() + 2 * 60 * 60 * 1000);
  const fmt = (d: Date) => d.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}/, '');
  const title = (locale === 'es' && event.titleEs ? event.titleEs : event.title) || 'Spanglish';
  const params = new URLSearchParams({
    action: 'TEMPLATE',
    text: title,
    dates: `${fmt(start)}/${fmt(end)}`,
    location: event.location || '',
    ctz: EVENT_TIMEZONE,
  });
  return `https://www.google.com/calendar/render?${params.toString()}`;
}

/**
 * Share a single (spare) ticket's QR with a guest. Uses the Web Share API when
 * available, otherwise copies the ticket link to the clipboard.
 */
export async function shareTicket(
  ticketId: string,
  eventTitle: string,
  locale: string
): Promise<'shared' | 'copied' | 'failed'> {
  const url = ticketScanUrl(ticketId);
  const text =
    locale === 'es'
      ? `Tu entrada para ${eventTitle}`
      : `Your ticket for ${eventTitle}`;
  try {
    if (typeof navigator !== 'undefined' && (navigator as any).share) {
      await (navigator as any).share({ title: eventTitle, text, url });
      return 'shared';
    }
    if (typeof navigator !== 'undefined' && navigator.clipboard) {
      await navigator.clipboard.writeText(url);
      return 'copied';
    }
    return 'failed';
  } catch (err: any) {
    // User cancelled the native share sheet.
    if (err?.name === 'AbortError') return 'shared';
    return 'failed';
  }
}

/** True when the event's calendar date is today (Asunción time). */
export function isToday(dateStr?: string): boolean {
  if (!dateStr) return false;
  const opts: Intl.DateTimeFormatOptions = {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    timeZone: EVENT_TIMEZONE,
  };
  const fmt = (d: Date) => d.toLocaleDateString('en-CA', opts);
  return fmt(parseDate(dateStr)) === fmt(new Date());
}
