import path from "node:path";
import fs from "node:fs/promises";
import yaml from "js-yaml";
import { XMLParser } from "fast-xml-parser";
import Database from "better-sqlite3";

const CHANNELS_URL = "https://www.jambox.pl/xml/listakanalow.xml";

const DB_PATH =
  process.env.FUZ_DB_PATH ||
  path.join(process.cwd(), "src", "data", "ServicesRange.db");

function isAuthorized(request) {
  const expected = import.meta.env.JAMBOX_ADMIN_TOKEN;
  if (!expected) return false;
  const token = request.headers.get("x-admin-token");
  return token === expected;
}

function getDb() {
  const db = new Database(DB_PATH);
  db.pragma("journal_mode = WAL");
  return db;
}

async function fetchXml(url) {
  const res = await fetch(url, {
    headers: { accept: "application/xml,text/xml,*/*" },
  });
  if (!res.ok) throw new Error(`XML HTTP ${res.status} for ${url}`);
  return await res.text();
}

function parseNodes(xmlText) {
  const parser = new XMLParser({
    ignoreAttributes: false,
    attributeNamePrefix: "@_",
    trimValues: true,
  });
  const json = parser.parse(xmlText);
  const nodes = json?.xml?.node ?? json?.node ?? [];
  return Array.isArray(nodes) ? nodes : [nodes];
}

function toInt(v) {
  const n = Number(v);
  return Number.isFinite(n) ? n : null;
}

function parseCsvIds(v) {
  const s = String(v ?? "").trim();
  if (!s) return [];
  return s
    .split(",")
    .map((x) => toInt(x.trim()))
    .filter((n) => n != null);
}

function decodeEntities(input) {
  if (!input) return "";
  let s = String(input)
    .replace(/&nbsp;/g, " ")
    .replace(/&ndash;/g, "–")
    .replace(/&quot;/g, '"')
    .replace(/&apos;/g, "'")
    .replace(/&#39;/g, "'")
    .replace(/&amp;/g, "&")
    .replace(/&lt;/g, "<")
    .replace(/&gt;/g, ">");
  s = s.replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d)));
  s = s.replace(/&#x([0-9a-fA-F]+);/g, (_, h) =>
    String.fromCodePoint(parseInt(h, 16)),
  );
  return s;
}

function htmlToMarkdown(input) {
  if (!input) return "";

  let html = "";
  if (typeof input === "string") html = input;
  else if (input?.p) {
    if (typeof input.p === "string") html = `<p>${input.p}</p>`;
    else if (Array.isArray(input.p))
      html = input.p.map((p) => `<p>${p}</p>`).join("");
  } else html = String(input);

  let s = decodeEntities(html);

  s = s
    .replace(/<\s*(ul|ol)[^>]*>/gi, "\n__LIST_START__\n")
    .replace(/<\/\s*(ul|ol)\s*>/gi, "\n__LIST_END__\n")
    .replace(/<\s*li[^>]*>/gi, "__LI__")
    .replace(/<\/\s*li\s*>/gi, "\n")
    .replace(/<\s*br\s*\/?\s*>/gi, "\n")
    .replace(/<\/\s*(p|div)\s*>/gi, "\n")
    .replace(/<\s*(p|div)[^>]*>/gi, "")
    .replace(/<[^>]+>/g, "")
    .replace(/\r/g, "")
    .replace(/[ \t]+\n/g, "\n")
    .replace(/\n{3,}/g, "\n\n")
    .trim();

  const lines = s.split("\n").map((x) => x.trim());
  const out = [];
  let inList = false;

  for (const line of lines) {
    if (!line) {
      if (!inList) out.push("");
      continue;
    }
    if (line === "__LIST_START__") {
      inList = true;
      continue;
    }
    if (line === "__LIST_END__") {
      inList = false;
      out.push("");
      continue;
    }
    if (inList && line.startsWith("__LI__")) {
      out.push(`- ${line.replace("__LI__", "").trim()}`);
      continue;
    }
    out.push(line);
  }

  return out.join("\n").trim();
}

function extractLogoUrl(node) {
  const logo = node?.field_logo_fid;
  if (!logo) return null;

  // w listakanalow.xml jest zwykle bezpośredni URL
  if (typeof logo === "string") {
    const s = logo.trim();
    if (!s) return null;
    const m = s.match(/src="([^"]+)"/);
    return m?.[1] ?? s;
  }

  if (logo?.img?.["@_src"]) return logo.img["@_src"];
  return null;
}

async function downloadLogoAsBase64(url) {
  try {
    const res = await fetch(url);
    if (!res.ok) return null;
    const ct = res.headers.get("content-type") || "image/png";
    const buf = Buffer.from(await res.arrayBuffer());
    if (!buf.length) return null;
    return `data:${ct};base64,${buf.toString("base64")}`;
  } catch {
    return null;
  }
}

function ensureTidColumn(db) {
  const cols = db
    .prepare("PRAGMA table_info(jambox_channels)")
    .all()
    .map((r) => String(r.name || "").toLowerCase());

  if (!cols.includes("tid")) {
    db.exec("ALTER TABLE jambox_channels ADD COLUMN tid INTEGER;");
  }
}

export async function POST({ request }) {
  if (!isAuthorized(request)) {
    return new Response(JSON.stringify({ ok: false, error: "Unauthorized" }), {
      status: 401,
      headers: { "content-type": "application/json; charset=utf-8" },
    });
  }

  const body = await request.json().catch(() => ({}));
  const dryRun = body?.dryRun === true;

  const YAML_PATH =
    import.meta.env.JAMBOX_TV_ADDONS_YAML_PATH ||
    path.join(
      process.cwd(),
      "src",
      "content",
      "internet-telewizja",
      "tv-addons.yaml",
    );

  const db = getDb();

  // tabela + unikalność do ON CONFLICT
  db.exec(`
    CREATE TABLE IF NOT EXISTS jambox_channels (
      id          INTEGER PRIMARY KEY AUTOINCREMENT,
      nazwa       TEXT    NOT NULL,
      pckg_name   TEXT    NOT NULL,
      image       TEXT,
      opis        TEXT,
      pckg_addons INTEGER NOT NULL DEFAULT (0)
    );
    CREATE UNIQUE INDEX IF NOT EXISTS ux_jambox_channels_name_pkg
    ON jambox_channels(nazwa, pckg_name);
  `);

  // ✅ dorób kolumnę tid (dla addonów)
  ensureTidColumn(db);

  const delAddons = db.prepare(
    `DELETE FROM jambox_channels WHERE pckg_addons = 1`,
  );

  // ✅ zapisujemy pckg_addons=1 i tid
  const upsert = db.prepare(`
    INSERT INTO jambox_channels (nazwa, pckg_name, image, opis, pckg_addons, tid)
    VALUES (@nazwa, @pckg_name, @image, @opis, 1, @tid)
    ON CONFLICT(nazwa, pckg_name) DO UPDATE SET
      image       = COALESCE(excluded.image, jambox_channels.image),
      opis        = COALESCE(excluded.opis,  jambox_channels.opis),
      pckg_addons = 1,
      tid         = COALESCE(excluded.tid, jambox_channels.tid)
  `);

  const logoCache = new Map();
  const rows = [];

  try {
    // 1) YAML -> tid dodatku
    const rawYaml = await fs.readFile(YAML_PATH, "utf8");
    const doc = yaml.load(rawYaml);

    if (!doc || !Array.isArray(doc.dodatki)) {
      return new Response(
        JSON.stringify({ ok: false, error: "YAML: brak doc.dodatki" }),
        {
          status: 400,
          headers: { "content-type": "application/json; charset=utf-8" },
        },
      );
    }

    // tid -> nazwa pakietu (z YAML)
    const addonTidToName = new Map();
    for (const a of doc.dodatki) {
      const tid = toInt(a?.tid);
      const name = String(a?.nazwa ?? "").trim();
      if (tid != null && name) addonTidToName.set(tid, name);
    }
    const addonTids = new Set(addonTidToName.keys());

    // 2) XML kanałów
    const xml = await fetchXml(CHANNELS_URL);
    const nodes = parseNodes(xml);

    for (const node of nodes) {
      const channelName = String(
        node?.nazwa_kanalu ?? node?.nazwa ?? "",
      ).trim();
      if (!channelName) continue;

      const pkgIds = parseCsvIds(node?.pakiety_id);
      if (!pkgIds.length) continue;

      // interesują nas tylko te pakiety_id, które są w YAML dodatków (tid)
      const matchedAddonTids = pkgIds.filter((tid) => addonTids.has(tid));
      if (!matchedAddonTids.length) continue;

      const opis = htmlToMarkdown(node?.opis) || null;

      const key = channelName.toLowerCase();
      let img = logoCache.get(key);
      if (img === undefined) {
        const logoUrl = extractLogoUrl(node);
        img = logoUrl ? await downloadLogoAsBase64(logoUrl) : null;
        logoCache.set(key, img);
      }

      for (const tid of matchedAddonTids) {
        const pckgName = addonTidToName.get(tid);
        if (!pckgName) continue;

        rows.push({
          nazwa: channelName,
          pckg_name: pckgName,
          image: img,
          opis,
          tid, // ✅ kluczowe: zapis tid do bazy
        });
      }
    }

    // 3) REFRESH: usuń wszystkie addonowe i wrzuć nowe
    if (!dryRun) {
      const trx = db.transaction((data) => {
        const info = delAddons.run();
        for (const r of data) upsert.run(r);
        return info.changes;
      });

      const deleted = trx(rows);

      return new Response(
        JSON.stringify({
          ok: true,
          dryRun,
          deleted_addon_rows: deleted,
          inserted_rows: rows.length,
          db: DB_PATH,
          yaml: YAML_PATH,
          uniqueAddonsUsed: addonTidToName.size,
          tidsUsed: addonTids.size,
        }),
        { headers: { "content-type": "application/json; charset=utf-8" } },
      );
    }

    return new Response(
      JSON.stringify({
        ok: true,
        dryRun: true,
        would_delete_addon_rows: "(unknown in dryRun)",
        would_insert_rows: rows.length,
        db: DB_PATH,
        yaml: YAML_PATH,
        uniqueAddonsUsed: addonTidToName.size,
        tidsUsed: addonTids.size,
      }),
      { headers: { "content-type": "application/json; charset=utf-8" } },
    );
  } catch (e) {
    console.error("import addon jambox_channels:", e);
    return new Response(
      JSON.stringify({ ok: false, error: String(e?.message || e) }),
      {
        status: 500,
        headers: { "content-type": "application/json; charset=utf-8" },
      },
    );
  } finally {
    try {
      db.close();
    } catch {}
  }
}
