// Cliente Xtream (navegador): categorías, items (MediaItem), búsqueda y URLs de
// reproducción. Todas las llamadas pasan por el proxy Node (/api/proxy) con el
// User-Agent de reproductor → funciona con servidores que bloquean el UA del
// navegador, y sin CORS. El VIDEO se reproduce igual con ese UA.
import { proxiedUrl } from "./http";
import { loadXtreamAccount, type XtreamAccount } from "./xtreamAccount";
import type { CatalogConfig, MediaItem, XtreamRef } from "./types";

export const PLAYER_UA = "IPTVSmartersPlayer";
const UA_HEADERS = { "User-Agent": PLAYER_UA };

function playerApi(acc: XtreamAccount, action?: string, extra?: Record<string, string | number>): string {
  const u = new URL(`${acc.server.replace(/\/+$/, "")}/player_api.php`);
  u.searchParams.set("username", acc.username);
  u.searchParams.set("password", acc.password);
  if (action) u.searchParams.set("action", action);
  for (const [k, v] of Object.entries(extra ?? {})) u.searchParams.set(k, String(v));
  return u.toString();
}

async function xtFetch<T>(url: string): Promise<T | null> {
  try {
    const res = await fetch(proxiedUrl(url, UA_HEADERS), { cache: "no-store" });
    if (!res.ok) return null;
    return (await res.json()) as T;
  } catch {
    return null;
  }
}

export function xtreamMovieUrl(x: XtreamRef): string {
  return `${x.server.replace(/\/+$/, "")}/movie/${encodeURIComponent(x.username)}/${encodeURIComponent(x.password)}/${x.streamId}.${x.ext || "mp4"}`;
}
export function xtreamEpisodeUrl(x: XtreamRef, epId: string | number, ext = "mp4"): string {
  return `${x.server.replace(/\/+$/, "")}/series/${encodeURIComponent(x.username)}/${encodeURIComponent(x.password)}/${epId}.${ext}`;
}

function img(u?: string): string | undefined {
  const s = (u ?? "").trim();
  return s && /^https?:\/\//i.test(s) ? s : undefined;
}
function yearFrom(added?: string): string | undefined {
  const n = Number(added);
  if (!n) return undefined;
  const y = new Date(n * 1000).getFullYear();
  return Number.isFinite(y) ? String(y) : undefined;
}

type Cat = { category_id: string; category_name: string };
type Vod = { name: string; title?: string; year?: string; plot?: string; stream_id: number | string; stream_icon?: string; category_id?: string; container_extension?: string; rating?: string | number; added?: string };
type Ser = { name: string; title?: string; year?: string; series_id: number | string; cover?: string; category_id?: string; rating?: string | number; plot?: string };

function mapVod(s: Vod, acc: XtreamAccount): MediaItem | null {
  const id = Number(s.stream_id);
  if (!id || !(s.title || s.name)) return null;
  const image = img(s.stream_icon);
  return {
    // Datos del SERVIDOR (siempre correctos): título limpio, año, sinopsis, póster.
    id, title: (s.title || s.name).trim(), mediaType: "movie", image, backdrop: image ?? null,
    overview: s.plot, rating: s.rating != null ? String(s.rating) : undefined,
    year: (s.year && String(s.year)) || yearFrom(s.added),
    xtream: { kind: "vod", server: acc.server, username: acc.username, password: acc.password, streamId: id, ext: s.container_extension || "mp4" }
  };
}
function mapSeries(s: Ser, acc: XtreamAccount): MediaItem | null {
  const id = Number(s.series_id);
  if (!id || !(s.title || s.name)) return null;
  const image = img(s.cover);
  return {
    id, title: (s.title || s.name).trim(), mediaType: "tv", image, backdrop: image ?? null,
    rating: s.rating != null ? String(s.rating) : undefined, overview: s.plot,
    year: s.year && String(s.year),
    xtream: { kind: "series", server: acc.server, username: acc.username, password: acc.password, seriesId: id }
  };
}

/** Una fila del Home por cada categoría de películas y series del servidor. */
export async function fetchXtreamCatalogs(): Promise<CatalogConfig[]> {
  const acc = loadXtreamAccount();
  if (!acc?.server) return [];
  const [vod, ser] = await Promise.all([
    xtFetch<Cat[]>(playerApi(acc, "get_vod_categories")),
    xtFetch<Cat[]>(playerApi(acc, "get_series_categories"))
  ]);
  const out: CatalogConfig[] = [];
  for (const c of vod ?? []) if (c?.category_id) out.push({ id: `xt_vod_${c.category_id}`, name: c.category_name || "Películas", sourceType: "xtream", mediaType: "movie", sourceRef: String(c.category_id), enabled: true, layout: "poster" });
  for (const c of ser ?? []) if (c?.category_id) out.push({ id: `xt_series_${c.category_id}`, name: c.category_name || "Series", sourceType: "xtream", mediaType: "tv", sourceRef: String(c.category_id), enabled: true, layout: "poster" });
  return out;
}

export async function loadXtreamCategory(catalog: CatalogConfig): Promise<MediaItem[]> {
  const acc = loadXtreamAccount();
  if (!acc?.server || !catalog.sourceRef) return [];
  const isSeries = catalog.mediaType === "tv";
  const list = await xtFetch<Array<Vod | Ser>>(playerApi(acc, isSeries ? "get_series" : "get_vod_streams", { category_id: catalog.sourceRef }));
  return (list ?? []).map((s) => (isSeries ? mapSeries(s as Ser, acc) : mapVod(s as Vod, acc))).filter(Boolean) as MediaItem[];
}

let ALL_CACHE: { server: string; items: MediaItem[]; at: number } | null = null;
export async function searchXtream(query: string): Promise<MediaItem[]> {
  const q = query.trim().toLowerCase();
  if (!q) return [];
  const acc = loadXtreamAccount();
  if (!acc?.server) return [];
  if (!ALL_CACHE || ALL_CACHE.server !== acc.server || Date.now() - ALL_CACHE.at > 300000) {
    const [vod, ser] = await Promise.all([
      xtFetch<Vod[]>(playerApi(acc, "get_vod_streams")),
      xtFetch<Ser[]>(playerApi(acc, "get_series"))
    ]);
    const items = [...(vod ?? []).map((s) => mapVod(s, acc)), ...(ser ?? []).map((s) => mapSeries(s, acc))].filter(Boolean) as MediaItem[];
    ALL_CACHE = { server: acc.server, items, at: Date.now() };
  }
  return ALL_CACHE.items.filter((i) => i.title.toLowerCase().includes(q)).slice(0, 60);
}

function readTmdbId(info: Record<string, unknown> | undefined): number | undefined {
  const raw = info?.tmdb_id ?? info?.tmdb ?? info?.tmdbid;
  const n = Number(raw);
  return Number.isFinite(n) && n > 0 ? n : undefined;
}

/** Info detallada de película: duración + tmdb_id del servidor (si lo trae). */
export async function fetchVodInfo(x: XtreamRef): Promise<{ duration?: string; tmdbId?: number }> {
  const acc = { server: x.server, username: x.username, password: x.password } as XtreamAccount;
  const data = await xtFetch<{ info?: Record<string, unknown> }>(playerApi(acc, "get_vod_info", { vod_id: String(x.streamId) }));
  return { duration: data?.info?.duration as string | undefined, tmdbId: readTmdbId(data?.info) };
}

/** Info de serie: temporadas + mapa de episodios (para reproducir). */
export async function fetchSeriesInfo(x: XtreamRef): Promise<{
  seasons: { id: number; seasonNumber: number; name: string }[];
  episodes: Record<string, { id: string; ext: string; title?: string; num?: number; plot?: string }>;
  tmdbId?: number;
}> {
  const acc = { server: x.server, username: x.username, password: x.password } as XtreamAccount;
  const data = await xtFetch<{
    info?: Record<string, unknown>;
    seasons?: Array<{ season_number?: number | string; name?: string }>;
    episodes?: Record<string, Array<{ id: string | number; episode_num: number | string; container_extension?: string; title?: string; info?: { plot?: string } }>>;
  }>(playerApi(acc, "get_series_info", { series_id: String(x.seriesId) }));
  const episodes: Record<string, { id: string; ext: string; title?: string; num?: number; plot?: string }> = {};
  const seasonNums = new Set<number>();
  for (const [sk, list] of Object.entries(data?.episodes ?? {})) {
    const sNum = Number(sk);
    for (const ep of list ?? []) {
      const eNum = Number(ep.episode_num);
      if (!eNum) continue;
      seasonNums.add(sNum);
      episodes[`${sNum}:${eNum}`] = { id: String(ep.id), ext: ep.container_extension || "mp4", title: ep.title || `Episodio ${eNum}`, num: eNum, plot: ep.info?.plot };
    }
  }
  const seasons = (data?.seasons?.length
    ? data.seasons.map((se) => ({ id: Number(se.season_number) || 1, seasonNumber: Number(se.season_number) || 1, name: se.name || `Temporada ${se.season_number}` }))
    : [...seasonNums].sort((a, b) => a - b).map((n) => ({ id: n, seasonNumber: n, name: `Temporada ${n}` })));
  return { seasons, episodes, tmdbId: readTmdbId(data?.info) };
}
