import type {
  LiveStream,
  MovieDetailsResponse,
  MovieStream,
  SeriesDetailsResponse,
  SeriesEpisode,
  SeriesStream,
  XtreamAccount,
  XtreamCatalog,
  XtreamCategory,
  XtreamCredentials,
} from "./types";
import { emptyCatalog } from "./types";

export class XtreamApiError extends Error {
  constructor(message: string, public readonly code: "network" | "auth" | "server" | "invalid") {
    super(message);
    this.name = "XtreamApiError";
  }
}

export function normalizeServerUrl(input: string): string {
  let value = input.trim();
  if (!value) {
    throw new XtreamApiError("Informe a URL do servidor.", "invalid");
  }

  if (!/^https?:\/\//i.test(value)) {
    value = `http://${value}`;
  }

  try {
    const parsed = new URL(value);
    if (!parsed.hostname) {
      throw new Error("missing host");
    }
    parsed.pathname = parsed.pathname.replace(/\/(player_api|panel_api)\.php\/?$/i, "");
    parsed.search = "";
    parsed.hash = "";
    return parsed.toString().replace(/\/$/, "");
  } catch {
    throw new XtreamApiError("A URL do servidor não parece válida.", "invalid");
  }
}

function encode(value: string | number): string {
  return encodeURIComponent(String(value));
}

function apiUrl(credentials: XtreamCredentials, action?: string): string {
  const base = normalizeServerUrl(credentials.serverUrl);
  const query = new URLSearchParams({
    username: credentials.username,
    password: credentials.password,
  });
  if (action) query.set("action", action);
  return `${base}/player_api.php?${query.toString()}`;
}

async function fetchJson<T>(url: string): Promise<T> {
  let response: Response;
  try {
    response = await fetch(url, {
      headers: { Accept: "application/json" },
    });
  } catch {
    throw new XtreamApiError(
      "Não foi possível alcançar o servidor. Verifique a conexão e a URL.",
      "network",
    );
  }

  if (!response.ok) {
    throw new XtreamApiError(
      `O servidor respondeu com o código ${response.status}.`,
      response.status === 401 || response.status === 403 ? "auth" : "server",
    );
  }

  try {
    return (await response.json()) as T;
  } catch {
    throw new XtreamApiError("O servidor retornou uma resposta inválida.", "server");
  }
}

export async function authenticate(credentials: XtreamCredentials): Promise<XtreamAccount> {
  const normalized: XtreamCredentials = {
    serverUrl: normalizeServerUrl(credentials.serverUrl),
    username: credentials.username.trim(),
    password: credentials.password,
  };

  if (!normalized.username || !normalized.password) {
    throw new XtreamApiError("Informe usuário e senha para continuar.", "invalid");
  }

  const data = await fetchJson<XtreamAccount>(apiUrl(normalized));
  const userInfo = data?.userInfo;
  if (!userInfo || Number(userInfo.auth) !== 1) {
    throw new XtreamApiError("Usuário ou senha inválidos, ou a conta está inativa.", "auth");
  }

  return {
    userInfo,
    serverInfo: data.serverInfo,
  };
}

function safeArray<T>(value: T[] | null | undefined): T[] {
  return Array.isArray(value) ? value : [];
}

export async function loadCatalog(credentials: XtreamCredentials): Promise<XtreamCatalog> {
  const [liveCategories, movieCategories, seriesCategories, liveStreams, movies, series] =
    await Promise.all([
      fetchJson<XtreamCategory[]>(apiUrl(credentials, "get_live_categories")),
      fetchJson<XtreamCategory[]>(apiUrl(credentials, "get_vod_categories")),
      fetchJson<XtreamCategory[]>(apiUrl(credentials, "get_series_categories")),
      fetchJson<LiveStream[]>(apiUrl(credentials, "get_live_streams")),
      fetchJson<MovieStream[]>(apiUrl(credentials, "get_vod_streams")),
      fetchJson<SeriesStream[]>(apiUrl(credentials, "get_series")),
    ]);

  return {
    liveCategories: safeArray(liveCategories),
    movieCategories: safeArray(movieCategories),
    seriesCategories: safeArray(seriesCategories),
    liveStreams: safeArray(liveStreams),
    movies: safeArray(movies),
    series: safeArray(series),
  };
}

export async function loadMovieInfo(
  credentials: XtreamCredentials,
  movieId: number,
): Promise<MovieDetailsResponse> {
  return fetchJson<MovieDetailsResponse>(apiUrl(credentials, "get_vod_info") + `&vod_id=${encode(movieId)}`);
}

export async function loadSeriesInfo(
  credentials: XtreamCredentials,
  seriesId: number,
): Promise<SeriesDetailsResponse> {
  return fetchJson<SeriesDetailsResponse>(apiUrl(credentials, "get_series_info") + `&series_id=${encode(seriesId)}`);
}

export function streamUrl(
  credentials: XtreamCredentials,
  kind: "live" | "movie" | "series",
  id: number | string,
  extension?: string,
): string {
  const base = normalizeServerUrl(credentials.serverUrl);
  const folder = kind === "live" ? "live" : kind === "movie" ? "movie" : "series";
  const suffix = kind === "live" ? extension || "m3u8" : extension || "mp4";
  return `${base}/${folder}/${encode(credentials.username)}/${encode(credentials.password)}/${encode(id)}.${encode(suffix)}`;
}

export function formatExpiry(value: string | number | null | undefined): string {
  if (!value) return "Sem data de expiração";
  const timestamp = Number(value);
  if (!Number.isFinite(timestamp)) return String(value);
  return new Intl.DateTimeFormat("pt-BR", { dateStyle: "medium" }).format(
    new Date(timestamp * 1000),
  );
}

export function getItemImage(item: LiveStream | MovieStream | SeriesStream): string | undefined {
  if ("cover" in item) return item.cover;
  if ("stream_icon" in item) return item.stream_icon;
  return undefined;
}

export function getCatalogCount(catalog: XtreamCatalog): number {
  return catalog.liveStreams.length + catalog.movies.length + catalog.series.length;
}
