/**
 * CineMall scraper (Lebanon — Le Mall Dbayeh)
 *
 * Strategy:
 *  - Parse the home page movie grid for active ticket slugs and metadata.
 *  - For each ticket slug, parse movieId from inline JS.
 *  - Fetch session JSON from v.php?function=sessionList&movieId=...
 */

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

const BASE = 'https://www.cine-mall.com';
const HOME_PATH = '/home';
const DEFAULT_CINEMA_ID = 'cinemall-dbayeh';
const DEFAULT_MAX_MOVIES = 24;
const HEADERS = {
  'User-Agent': 'Mozilla/5.0 (compatible; CultRoll/1.0)',
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
};

interface CinemallOpts {
  baseUrl?: string;
  homePath?: string;
  cinemaId?: string;
  maxMovies?: number;
}

interface HomeMovieCard {
  slug: string;
  title: string;
  durationMin?: number;
  genre?: string;
  rating?: string;
  posterUrl?: string;
  imdbId?: string;
}

interface SessionRecord {
  sessionId: string;
  movieId: string;
  sessionTimeFormatted?: string;
  sessionTimeDateFormatted?: string;
  sessionTime?: string;
  cinema?: string;
}

type SessionListResponse = Record<string, SessionRecord[]>;

export async function scrapeCinemall(_env: ScraperEnv, opts?: CinemallOpts): Promise<ScraperResult> {
  const baseUrl = (opts?.baseUrl ?? BASE).replace(/\/+$/, '');
  const homePath = opts?.homePath ?? HOME_PATH;
  const cinemaId = opts?.cinemaId ?? DEFAULT_CINEMA_ID;
  const maxMovies = clampMaxMovies(opts?.maxMovies);

  const homeHtml = await fetchText(new URL(homePath, `${baseUrl}/`).toString());
  const cards = parseHomeMovieCards(homeHtml).slice(0, maxMovies);

  const titles = new Map<string, ScrapedTitle>();
  const showtimeMap = new Map<string, ScrapedShowtime>();

  for (const card of cards) {
    let title = titles.get(card.slug);
    if (!title) {
      title = {
        chainMovieId: card.slug,
        title_en: card.title,
        duration_min: card.durationMin,
        genre: card.genre,
        exhibitor_rating: card.rating,
        poster_url: card.posterUrl,
        imdb_id: card.imdbId,
        is_coming_soon: true,
      };
      titles.set(card.slug, title);
    }

    const ticketUrl = new URL(`ticket/${card.slug}`, `${baseUrl}/`).toString();
    let ticketHtml: string;
    try {
      ticketHtml = await fetchText(ticketUrl);
    } catch (err) {
      console.error(`CineMall: failed ticket page ${card.slug}:`, err);
      continue;
    }

    const movieId = extractMovieId(ticketHtml);
    const synopsis = extractSynopsis(ticketHtml);
    if (!title.synopsis_en && synopsis) title.synopsis_en = synopsis;

    if (!movieId) {
      continue;
    }

    let sessions: SessionListResponse = {};
    try {
      sessions = await fetchSessionList(baseUrl, movieId);
    } catch (err) {
      console.error(`CineMall: session list failed ${card.slug} (${movieId}):`, err);
      continue;
    }

    let hasShowtimes = false;
    for (const daySessions of Object.values(sessions)) {
      for (const session of daySessions ?? []) {
        const showtime = normalizeSessionDateTime(session);
        if (!showtime) continue;
        const sessionId = String(session.sessionId || '').trim();
        if (!sessionId) continue;

        const stId = `${cinemaId}-${card.slug}-${sessionId}`;
        showtimeMap.set(stId, {
          id: stId,
          cinema_id: cinemaId,
          title_id: card.slug,
          showtime,
          screen_type: normalizeScreenType(session.cinema),
          booking_url: ticketUrl,
        });
        hasShowtimes = true;
      }
    }

    if (hasShowtimes) {
      title.is_coming_soon = false;
    }
  }

  return {
    chainId: 'cinemall',
    titles: Array.from(titles.values()),
    showtimes: Array.from(showtimeMap.values()),
  };
}

function parseHomeMovieCards(html: string): HomeMovieCard[] {
  const cards: HomeMovieCard[] = [];
  const seen = new Set<string>();

  const figureRe = /<figure[^>]*class="effect-goliath"[^>]*>([\s\S]*?)<\/figure>/gi;
  let figureMatch: RegExpExecArray | null;
  while ((figureMatch = figureRe.exec(html)) !== null) {
    const block = figureMatch[1];
    const slug = extract(block, /href="ticket\/([^"?#]+)"/i);
    if (!slug) continue;
    if (seen.has(slug)) continue;
    seen.add(slug);

    const title = extract(block, /<p[^>]*class="title"[^>]*>([\s\S]*?)<\/p>/i) ?? humanizeSlug(slug);
    const runtimeText = extract(block, /<p[^>]*class="runtime"[^>]*>[\s\S]*?runtime:\s*([^<]+)<\/p>/i);
    const durationMin = runtimeText ? parseDuration(runtimeText) : undefined;
    const rating = extract(block, /<span[^>]*class="rating-number"[^>]*>([\s\S]*?)<\/span>/i) ?? undefined;
    const genre = extractGenre(block);
    const posterUrl = absolutize(
      extractAttr(block, /<img[^>]*class="pure-img[^"]*"[^>]*src="([^"]+)"/i),
      BASE
    );
    const imdbId = extract(block, /data-title="(tt\d+)"/i) ?? undefined;

    cards.push({
      slug,
      title,
      durationMin,
      genre,
      rating,
      posterUrl,
      imdbId,
    });
  }

  return cards;
}

function extractMovieId(ticketHtml: string): string | null {
  return ticketHtml.match(/sessionList&movieId=(\d+)/i)?.[1] ?? null;
}

function extractSynopsis(ticketHtml: string): string | undefined {
  const synopsis = extract(ticketHtml, /<b[^>]*>\s*Synopsis:\s*<\/b><br>\s*([\s\S]*?)<\/p>/i);
  return synopsis || undefined;
}

async function fetchSessionList(baseUrl: string, movieId: string): Promise<SessionListResponse> {
  const url = new URL('/v.php', `${baseUrl}/`);
  url.searchParams.set('function', 'sessionList');
  url.searchParams.set('movieId', movieId);

  const resp = await fetchWithTimeout(url.toString(), { headers: HEADERS }, {
    resource: `CineMall sessionList ${movieId}`,
  });
  if (!resp.ok) {
    throw new Error(`CineMall sessionList failed (${resp.status}) for movieId ${movieId}`);
  }
  return resp.json() as Promise<SessionListResponse>;
}

async function fetchText(url: string): Promise<string> {
  const resp = await fetchWithTimeout(url, { headers: HEADERS }, {
    resource: `CineMall ${url}`,
  });
  if (!resp.ok) {
    throw new Error(`CineMall fetch failed (${resp.status}) for ${url}`);
  }
  return resp.text();
}

function extract(html: string, re: RegExp): string | null {
  const m = html.match(re);
  if (!m) return null;
  const text = stripHtml(m[1]);
  return text || null;
}

function extractAttr(html: string, re: RegExp): string | null {
  return html.match(re)?.[1] ?? null;
}

function extractGenre(block: string): string | undefined {
  const genreBlock = block.match(/<ul[^>]*class="genre"[^>]*>([\s\S]*?)<\/ul>/i)?.[1];
  if (!genreBlock) return undefined;
  const values: string[] = [];
  const liRe = /<li>([\s\S]*?)<\/li>/gi;
  let liMatch: RegExpExecArray | null;
  while ((liMatch = liRe.exec(genreBlock)) !== null) {
    const value = stripHtml(liMatch[1]);
    if (value) values.push(value);
  }
  return values.length > 0 ? values.join(', ') : undefined;
}

function absolutize(url: string | null, baseUrl: string): string | undefined {
  if (!url) return undefined;
  const cleaned = url.replace(/&amp;/g, '&').trim();
  if (!cleaned) return undefined;
  try {
    return new URL(cleaned, `${baseUrl}/`).toString();
  } catch {
    return undefined;
  }
}

function normalizeSessionDateTime(session: SessionRecord): string | null {
  const rawIso = String(session.sessionTime ?? '').trim();
  if (rawIso) {
    const normalized = rawIso.replace(' ', 'T');
    if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(normalized)) {
      return normalized.slice(0, 19);
    }
  }

  const dateTime = String(session.sessionTimeDateFormatted ?? '').trim();
  if (/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}$/.test(dateTime)) {
    return `${dateTime.replace(/\s+/, 'T')}:00`;
  }

  return null;
}

function normalizeScreenType(cinemaLabel: string | undefined): string {
  const text = (cinemaLabel ?? '').toLowerCase();
  if (text.includes('vip')) return 'vip';
  if (text.includes('imax')) return 'imax';
  if (text.includes('4dx')) return '4dx';
  if (text.includes('3d')) return '3d';
  return 'standard';
}

function clampMaxMovies(value: number | undefined): number {
  if (!Number.isFinite(value)) return DEFAULT_MAX_MOVIES;
  return Math.max(1, Math.min(60, Math.floor(value as number)));
}

function humanizeSlug(slug: string): string {
  return slug
    .split('-')
    .filter(Boolean)
    .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
    .join(' ');
}
