/**
 * Stalker / MAC portal client (frontend side).
 *
 * Browsers cannot talk to a Stalker portal directly (custom forbidden headers
 * + CORS). We delegate to the backend `/api/stalker/*` endpoints, which
 * impersonate a MAG250 STB exactly per the Stalker Middleware spec.
 */
import type { Channel, Movie, Series } from "@/types/xtream";

const BASE = import.meta.env.VITE_BACKEND_URL || "";

export const MAC_REGEX = /^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/;

export const isValidMac = (mac: string): boolean =>
  MAC_REGEX.test(mac.trim());

interface StalkerChannelRaw {
  id: string;
  name: string;
  logo: string;
  category: string;
  categoryId: string;
  cmd: string;
  epgChannelId: string;
  tv_archive: number;
  censored: number;
}

interface StalkerCategoryRaw {
  id: string;
  name: string;
  censored: number;
}

const post = async <T>(path: string, body: unknown): Promise<T> => {
  const res = await fetch(`${BASE}${path}`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  let data: any = null;
  try {
    data = await res.json();
  } catch {
    /* not json */
  }
  if (!res.ok) {
    const detail =
      data?.detail ||
      (typeof data === "object" ? JSON.stringify(data) : "") ||
      `HTTP ${res.status}`;
    throw new Error(typeof detail === "string" ? detail : JSON.stringify(detail));
  }
  return data as T;
};

export interface StalkerHandshake {
  ok: boolean;
  token: string;
  portal_root: string;
  mac: string;
  profile: {
    id?: string;
    mac?: string;
    name?: string;
    phone?: string;
    tariff_plan?: string;
    blocked: boolean;
    expire_billing_date?: string;
  };
}

export const stalkerApi = {
  async handshake(portalUrl: string, mac: string): Promise<StalkerHandshake> {
    return post<StalkerHandshake>("/api/stalker/handshake", {
      portal_url: portalUrl,
      mac,
    });
  },

  async fetchAll(
    portalUrl: string,
    mac: string,
  ): Promise<{ channels: Channel[]; movies: Movie[]; series: Series[] }> {
    const ch = await post<{ channels: StalkerChannelRaw[] }>(
      "/api/stalker/all-channels",
      { portal_url: portalUrl, mac },
    );

    // Stalker returns the cmd that has to be resolved at play time. We store
    // it in `streamUrl` as a marker prefixed with `stalker-cmd:` and resolve
    // lazily when the user actually presses Play.
    const channels: Channel[] = (ch.channels || []).map((c) => ({
      id: c.id || c.epgChannelId || c.name,
      name: c.name,
      category: c.category || "Uncategorized",
      categoryId: c.categoryId || "0",
      streamUrl: `stalker-cmd:${c.cmd}`,
      logoUrl: c.logo || undefined,
      epgChannelId: c.epgChannelId || undefined,
    }));

    let categories: StalkerCategoryRaw[] = [];
    try {
      const vod = await post<{ categories: StalkerCategoryRaw[] }>(
        "/api/stalker/vod-categories",
        { portal_url: portalUrl, mac },
      );
      categories = vod.categories || [];
    } catch {
      categories = [];
    }

    // We don't yet pull each movie's stream — Stalker VOD lists are paginated
    // and can be huge. We expose categories as Movie placeholders so the UI
    // shows a "Movies" tab; clicking a category will trigger a per-category
    // fetch later. For now no concrete movies / series are returned.
    const movies: Movie[] = categories.map((c) => ({
      id: `cat-${c.id}`,
      name: c.name,
      category: c.name,
      categoryId: c.id,
      streamUrl: "",
    }));

    return { channels, movies, series: [] };
  },

  async resolveStream(
    portalUrl: string,
    mac: string,
    cmd: string,
  ): Promise<string> {
    const data = await post<{ url: string }>("/api/stalker/resolve-stream", {
      portal_url: portalUrl,
      mac,
      cmd,
    });
    return data.url;
  },
};
