/**
 * Grand Cinemas scraper (Lebanon + Jordan)
 *
 * Tech: jQuery server-rendered, AJAX cascading selects
 * Lebanon: POST lb.grandcinemasme.com/en/{action}.ashx
 * Jordan:  POST jo.grandcinemasme.com/handlers/{action}.ashx
 *
 * Flow:
 *   getmovies(cinemaId)                         -> movie list
 *   getsessionTime(cinemaId, movieId, date)      -> showtimes (value=sessionId, text=displayTime)
 *
 * NOTE: getsessionDate is intentionally skipped. The API accepts arbitrary date strings,
 * so we hardcode the next 2 days and spend the request budget on higher-priority cinemas first.
 *
 * Cinema IDs:
 *   Lebanon (lb.grandcinemasme.com):
 *     0000000004 - ABC Achrafieh
 *     0000000006 - ABC Dbayeh
 *     0000000008 - ABC Verdun
 *     0000000005 - The Spot Saida
 *   Jordan (jo.grandcinemasme.com):
 *     0000000002 - City Mall Amman
 *
 * Subrequest budget:
 *   - We still target a ceiling of 50 requests per invocation.
 *   - `max_movies` is treated as an upper bound, not a hard per-cinema slice.
 *   - We fetch all scoped cinema movie lists first, then distribute the remaining session budget
 *     in priority order so Beirut venues stay balanced when lower-priority venues are also present.
 */

import type { ScraperEnv, ScraperResult, ScrapedTitle, ScrapedShowtime } from '../types';
import { fetchWithTimeout } from '../utils/http';
import { stripHtml } from '../utils/parser';

const BASE_LB = 'https://lb.grandcinemasme.com';
const BASE_JO = 'https://jo.grandcinemasme.com';

export interface CinemaConfig {
  externalId: string;
  cinemaId: string; // Our DB ID
  base: string;
  pathPrefix: string; // '/en' for Lebanon, '/handlers' for Jordan
}

interface MovieOption {
  value: string;
  text: string;
}

export const DEFAULT_CINEMAS: CinemaConfig[] = [
  { externalId: '0000000004', cinemaId: 'grand-abc-achrafieh', base: BASE_LB, pathPrefix: '/en' },
  { externalId: '0000000006', cinemaId: 'grand-abc-dbayeh', base: BASE_LB, pathPrefix: '/en' },
  { externalId: '0000000008', cinemaId: 'grand-abc-verdun', base: BASE_LB, pathPrefix: '/en' },
  { externalId: '0000000002', cinemaId: 'grand-city-mall-amman', base: BASE_JO, pathPrefix: '/handlers' },
];

// Default per-cinema ceiling when no source-specific override is provided.
const MAX_MOVIES_PER_CINEMA = 5;
// Keep Grand under the legacy request ceiling while favoring higher-priority cinemas.
const MAX_SUBREQUESTS = 50;
// Days ahead to fetch showtimes for (today + N-1)
const LOOKAHEAD_DAYS = 2;

interface GrandOpts {
  cinemas?: CinemaConfig[];
  lookaheadDays?: number;
  maxMoviesPerCinema?: number;
}

export async function scrapeGrandCinemas(env: ScraperEnv, opts?: GrandOpts): Promise<ScraperResult> {
  const cinemas = opts?.cinemas ?? DEFAULT_CINEMAS;
  const lookaheadDays = opts?.lookaheadDays ?? LOOKAHEAD_DAYS;
  const maxMoviesPerCinema = opts?.maxMoviesPerCinema ?? MAX_MOVIES_PER_CINEMA;
  const movies: Map<string, ScrapedTitle> = new Map();
  const showtimes: ScrapedShowtime[] = [];
  let subrequestsUsed = 0;

  const dates: string[] = [];
  for (let i = 0; i < lookaheadDays; i++) {
    const d = new Date();
    d.setUTCDate(d.getUTCDate() + i);
    dates.push(d.toISOString().slice(0, 10)); // "YYYY-MM-DD"
  }

  const cinemaMovieLists: Array<{ cinema: CinemaConfig; movieOptions: MovieOption[] }> = [];

  for (const cinema of cinemas) {
    try {
      if (MAX_SUBREQUESTS - subrequestsUsed < 1) {
        console.warn(`Grand Cinemas: stopping before ${cinema.cinemaId}; request budget exhausted.`);
        break;
      }

      // Step 1: Get movies for this cinema (1 subrequest)
      const movieListHtml = await fetchAjax(cinema.base, cinema.pathPrefix, 'getmovies', { cinemaId: cinema.externalId });
      subrequestsUsed += 1;
      const movieOptions = parseSelectOptions(movieListHtml).filter((opt) => opt.value && opt.value !== '0');
      cinemaMovieLists.push({ cinema, movieOptions });
    } catch (err) {
      console.error(`Grand Cinemas: failed scraping ${cinema.cinemaId}:`, err);
    }
  }

  const totalMovieBudget = Math.floor((MAX_SUBREQUESTS - subrequestsUsed) / dates.length);
  const cinemaSelections = distributeMovieBudget(cinemaMovieLists, maxMoviesPerCinema, totalMovieBudget);

  for (const { cinema, movieOptions, selectedMovieOptions } of cinemaSelections) {
    if (selectedMovieOptions.length < movieOptions.length) {
      console.warn(
        `Grand Cinemas: limited ${cinema.cinemaId} to ${selectedMovieOptions.length}/${movieOptions.length} movies ` +
        `(cap=${maxMoviesPerCinema}, shared_budget=${totalMovieBudget}).`
      );
    }

    try {
      for (const movieOpt of selectedMovieOptions) {
        // chainMovieId includes region prefix so chain-posters enricher can build the correct URL
        const region = cinema.base.includes('lb.') ? 'lb' : 'jo';
        const chainMovieId = `${region}:${movieOpt.value}`;
        if (!movies.has(chainMovieId)) {
          movies.set(chainMovieId, {
            chainMovieId,
            title_en: movieOpt.text,
          });
        }

        // Step 2: Get session times for each hardcoded date (skips getsessionDate entirely)
        // Each call: 1 subrequest. Empty response = no sessions that day (safe to ignore).
        for (const dateStr of dates) {
          const timeListHtml = await fetchAjax(cinema.base, cinema.pathPrefix, 'getsessionTime', {
            cinemaId: cinema.externalId,
            movieId: movieOpt.value,
            date: dateStr,
          });
          subrequestsUsed += 1;
          const timeOptions = parseSelectOptions(timeListHtml);

          for (const timeOpt of timeOptions) {
            if (!timeOpt.value || timeOpt.value === '0') continue;

            // timeOpt.text = display time like "14:30"; timeOpt.value = numeric session ID
            const showtimeStr = buildDatetime(dateStr, timeOpt.text);
            const stId = `${cinema.cinemaId}-${chainMovieId.replace(':', '-')}-${showtimeStr}`;

            // Booking URL built from params — no getLink fetch needed
            const bookingUrl = `/book?chain=grand&session=${encodeURIComponent(timeOpt.value)}&cinema=${encodeURIComponent(cinema.externalId)}&date=${encodeURIComponent(dateStr)}&movie=${encodeURIComponent(movieOpt.value)}`;

            showtimes.push({
              id: stId,
              cinema_id: cinema.cinemaId,
              title_id: chainMovieId,
              showtime: showtimeStr,
              screen_type: detectScreenType(timeOpt.text),
              booking_url: bookingUrl,
            });
          }
        }
      }
    } catch (err) {
      console.error(`Grand Cinemas: failed scraping ${cinema.cinemaId}:`, err);
    }
  }

  return {
    chainId: 'grand-cinemas',
    titles: Array.from(movies.values()),
    showtimes,
  };
}

// --- Helpers ---

async function fetchAjax(base: string, pathPrefix: string, action: string, params: Record<string, string>): Promise<string> {
  const url = new URL(`${pathPrefix}/${action}.ashx`, base);
  const body = new URLSearchParams(params);

  const resp = await fetchWithTimeout(url.toString(), {
    method: 'POST',
    body,
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'Accept': 'text/html, */*',
      'X-Requested-With': 'XMLHttpRequest',
      'User-Agent': 'Mozilla/5.0 (compatible; CultRoll/1.0)',
    },
  }, {
    resource: `Grand AJAX ${action}`,
  });

  if (!resp.ok) throw new Error(`Grand AJAX ${action} failed: ${resp.status}`);
  return resp.text();
}

function parseSelectOptions(html: string): Array<{ value: string; text: string }> {
  const options: Array<{ value: string; text: string }> = [];
  const re = /<option\s+value=["']([^"']*)["'][^>]*>([^<]*)<\/option>/gi;
  let m: RegExpExecArray | null;
  while ((m = re.exec(html)) !== null) {
    options.push({ value: m[1], text: stripHtml(m[2]) });
  }
  return options;
}

function distributeMovieBudget(
  cinemaMovieLists: Array<{ cinema: CinemaConfig; movieOptions: MovieOption[] }>,
  maxMoviesPerCinema: number,
  totalMovieBudget: number
): Array<{ cinema: CinemaConfig; movieOptions: MovieOption[]; selectedMovieOptions: MovieOption[] }> {
  const cappedMovieLists = cinemaMovieLists.map(({ cinema, movieOptions }) => ({
    cinema,
    movieOptions,
    cappedMovieOptions: movieOptions.slice(0, maxMoviesPerCinema),
  }));
  const selectedMovieOptions = cappedMovieLists.map(() => [] as MovieOption[]);
  let remaining = totalMovieBudget;

  while (remaining > 0) {
    let assignedInPass = false;

    for (const [idx, entry] of cappedMovieLists.entries()) {
      const nextMovie = entry.cappedMovieOptions[selectedMovieOptions[idx].length];
      if (!nextMovie) continue;
      selectedMovieOptions[idx].push(nextMovie);
      remaining -= 1;
      assignedInPass = true;
      if (remaining === 0) break;
    }

    if (!assignedInPass) break;
  }

  return cappedMovieLists.map((entry, idx) => ({
    cinema: entry.cinema,
    movieOptions: entry.movieOptions,
    selectedMovieOptions: selectedMovieOptions[idx],
  }));
}

function buildDatetime(dateStr: string, timeStr: string): string {
  // Grand format: date like "2026-01-15", time like "19:30" or "7:30 PM"
  const pmMatch = timeStr.match(/(\d{1,2}):(\d{2})\s*(PM|AM)/i);
  if (pmMatch) {
    let h = parseInt(pmMatch[1], 10);
    const mm = pmMatch[2];
    if (pmMatch[3].toUpperCase() === 'PM' && h < 12) h += 12;
    if (pmMatch[3].toUpperCase() === 'AM' && h === 12) h = 0;
    return `${dateStr}T${h.toString().padStart(2, '0')}:${mm}:00`;
  }
  // Already 24h format
  return `${dateStr}T${timeStr.trim()}:00`;
}

function detectScreenType(text: string): string {
  const lower = text.toLowerCase();
  if (lower.includes('imax')) return 'imax';
  if (lower.includes('mx4d')) return 'mx4d';
  if (lower.includes('3d')) return '3d';
  if (lower.includes('vip') || lower.includes('grand class')) return 'vip';
  if (lower.includes('dolby') || lower.includes('atmos')) return 'dolby-atmos';
  return 'standard';
}
