// Single source of truth for event seat accounting.
//
// A seat is held by a booking the moment the money is real or claimed to be:
//   - ticket 'confirmed' or 'checked_in' (paid), or
//   - ticket 'pending' whose payment is 'pending_approval' (customer clicked
//     "I've paid" and is waiting for admin verification).
//
// A bare 'pending' payment — an opened checkout that was never paid nor claimed,
// on any provider — holds NO seat, so abandoned bookings can never block sales.
// The accepted trade-off is a small oversell window when several people book the
// last seats and all later pay/claim; admin approval is the backstop and may
// knowingly approve over capacity (see routes/payments.ts).
//
// 'pending_approval' is a payment status (the ticket row stays 'pending'), so
// every capacity count joins tickets to payments. There is exactly one payment
// row per ticket (created together in routes/tickets.ts).
//
// Every place that counts seats — booking creation, public availability,
// hold recovery, admin dashboards — must go through these builders so the
// formula cannot diverge between surfaces.

import { sql, and, eq, inArray } from 'drizzle-orm';
import { tickets, payments } from '../db/index.js';

// COALESCE keeps the predicate two-valued under the LEFT JOIN (a ticket with no
// payment row must count as "not holding" — NULL would poison NOT ...).
export const seatHoldingSql = sql`(${(tickets as any).status} IN ('confirmed', 'checked_in') OR (${(tickets as any).status} = 'pending' AND COALESCE(${(payments as any).status}, '') = 'pending_approval'))`;

/**
 * Query: number of seats currently held for an event.
 * `executor` is the db, or a transaction (sync sqlite tx: finish with `.get()`;
 * async pg tx / plain db: await via dbGet).
 */
export function seatHolderCountQuery(executor: any, eventId: string) {
  return executor
    .select({ count: sql<number>`count(distinct ${(tickets as any).id})` })
    .from(tickets)
    .leftJoin(payments, eq((payments as any).ticketId, (tickets as any).id))
    .where(and(eq((tickets as any).eventId, eventId), seatHoldingSql));
}

/**
 * Query: how many of the given tickets do NOT currently hold a seat (and so
 * would need fresh capacity if promoted to a seat-holding state).
 */
export function unseatedTicketCountQuery(executor: any, ticketIds: string[]) {
  return executor
    .select({ count: sql<number>`count(distinct ${(tickets as any).id})` })
    .from(tickets)
    .leftJoin(payments, eq((payments as any).ticketId, (tickets as any).id))
    .where(and(inArray((tickets as any).id, ticketIds), sql`NOT ${seatHoldingSql}`));
}

/**
 * Query: per-event breakdown of paid vs claimed seats, grouped by event.
 * paidCount = confirmed + checked_in; claimedCount = pending_approval-held.
 * Pass `eventId` to restrict to one event (still returns a grouped row).
 */
export function eventSeatBreakdownQuery(executor: any, eventId?: string) {
  const query = executor
    .select({
      eventId: (tickets as any).eventId,
      paidCount: sql<number>`sum(case when ${(tickets as any).status} IN ('confirmed', 'checked_in') then 1 else 0 end)`,
      claimedCount: sql<number>`sum(case when ${(tickets as any).status} = 'pending' AND COALESCE(${(payments as any).status}, '') = 'pending_approval' then 1 else 0 end)`,
    })
    .from(tickets)
    .leftJoin(payments, eq((payments as any).ticketId, (tickets as any).id));
  return (eventId ? query.where(eq((tickets as any).eventId, eventId)) : query)
    .groupBy((tickets as any).eventId);
}
