// YouTube cookies handling — bypasses YouTube's bot detection by either
// using an uploaded cookies.txt file OR (local dev only) live browser
// cookies via yt-dlp's --cookies-from-browser. Module owns its own
// state (file path, file-exists flag, browser-name fallback).
//
// Priority order at runtime:
//   1. cookies.txt file in DATA_DIR  (preferred — stable, OS-independent)
//   2. --cookies-from-browser <name> (set via YT_COOKIES_FROM in .env;
//                                     local Mac dev only)
//   3. no cookies                    (anonymous yt-dlp; YouTube may rate-
//                                     limit or require sign-in)

import { execFile } from "child_process";
import { promisify } from "util";
import fs from "fs/promises";
import path from "path";
import express from "express";

const execFileAsync = promisify(execFile);

// ── Module-private state ────────────────────────────────────────────────────
// Set by initCookies(); the cookies-file flag is also flipped by the
// upload/delete routes.
let ytCookiesBrowser = "";
let ytCookiesFileExists = false;
let ytCookiesFilePath = null;

// ── Initialization ──────────────────────────────────────────────────────────
// Call once at boot. Reads YT_COOKIES_FROM out of .env (if any) and
// checks whether cookies.txt is present. Idempotent.
export async function initCookies({ dataDir, envPath }) {
  ytCookiesFilePath = path.join(dataDir, "cookies.txt");
  try {
    const envContent = await fs.readFile(envPath, "utf-8").catch(() => "");
    const cm = envContent.match(/^YT_COOKIES_FROM=(.+)$/m);
    if (cm) ytCookiesBrowser = cm[1].trim().replace(/^["']|["']$/g, "").toLowerCase();
  } catch {}
  try {
    await fs.access(ytCookiesFilePath);
    ytCookiesFileExists = true;
    console.log("  🍪  Found cookies.txt — will use for YouTube authentication");
  } catch {
    ytCookiesFileExists = false;
  }
}

// ── yt-dlp arg helpers ──────────────────────────────────────────────────────
// Used by the /api/process pipeline (and subscription channel discovery)
// to spell out the right --cookies args based on what's available.
export function ytCookieArgs() {
  if (ytCookiesFileExists) return ["--cookies", ytCookiesFilePath];
  if (ytCookiesBrowser) return ["--cookies-from-browser", ytCookiesBrowser];
  return [];
}

// Extra yt-dlp args for robustness — currently just rate-limit sleep
// settings to avoid being flagged on batch operations.
export function ytExtraArgs() {
  return ["--sleep-interval", "1", "--max-sleep-interval", "3"];
}

// Human-readable description of which method is currently active.
// Surfaced by /api/health and /api/cookies/status.
export function ytCookieMethod() {
  if (ytCookiesFileExists) return "cookies.txt";
  if (ytCookiesBrowser) return ytCookiesBrowser;
  return "none";
}

// Lightweight read for the health check.
export function getCookieFilePath() {
  return ytCookiesFilePath;
}

// ── Routes ──────────────────────────────────────────────────────────────────
// Register /api/cookies/* on the given Express app. Pulls the active
// state from this module so a successful upload/delete is reflected in
// subsequent yt-dlp calls without re-import gymnastics.
export function setupCookieRoutes(app) {
  // Upload cookies.txt content
  app.post("/api/cookies/upload", express.text({ type: "*/*", limit: "2mb" }), async (req, res) => {
    try {
      const content = req.body;
      if (!content || typeof content !== "string" || content.length < 20) {
        return res.status(400).json({ error: "Invalid cookie file content" });
      }
      // Basic shape check — Netscape cookie format starts with a header
      // comment or has the tab-separated TRUE/FALSE markers.
      const firstLine = content.split("\n")[0].trim();
      const looksValid = /^#.*cookie|^#.*http|^\./i.test(firstLine) || content.includes("\tTRUE\t") || content.includes("\tFALSE\t");
      if (!looksValid) {
        return res.status(400).json({ error: "File doesn't look like a valid Netscape cookies.txt file. The first line should start with '# Netscape HTTP Cookie File' or '# HTTP Cookie File'." });
      }
      await fs.writeFile(ytCookiesFilePath, content, "utf-8");
      ytCookiesFileExists = true;
      console.log("  🍪  cookies.txt uploaded via web UI");
      res.json({ ok: true, message: "Cookies saved successfully" });
    } catch (err) {
      res.status(500).json({ error: err.message });
    }
  });

  app.post("/api/cookies/delete", async (req, res) => {
    try {
      await fs.unlink(ytCookiesFilePath).catch(() => {});
      ytCookiesFileExists = false;
      console.log("  🍪  cookies.txt deleted via web UI");
      res.json({ ok: true });
    } catch (err) {
      res.status(500).json({ error: err.message });
    }
  });

  // Lightweight check — fetches a known video's title to confirm cookies
  // unlock anonymous-restricted content.
  app.post("/api/cookies/test", async (req, res) => {
    try {
      const cookieArgs = ytCookieArgs();
      if (cookieArgs.length === 0) {
        return res.json({ ok: false, error: "No cookies configured" });
      }
      const { stdout } = await execFileAsync("yt-dlp", [
        "--print", "%(title)s",
        "--no-download",
        ...cookieArgs,
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
      ], { timeout: 20000 });
      const title = stdout.trim();
      if (title && title.length > 0) {
        res.json({ ok: true, message: `Cookies working! Fetched: "${title}"` });
      } else {
        res.json({ ok: false, error: "yt-dlp returned empty response" });
      }
    } catch (err) {
      const msg = (err.stderr || "") + " " + (err.message || "");
      const isAuth = /Sign in|bot|403/i.test(msg);
      res.json({ ok: false, error: isAuth ? "Cookies are expired or invalid — YouTube still requires sign-in" : msg.slice(0, 300) });
    }
  });

  app.get("/api/cookies/status", async (req, res) => {
    const info = { method: ytCookieMethod() };
    if (ytCookiesFileExists) {
      try {
        const stat = await fs.stat(ytCookiesFilePath);
        info.fileAgeDays = Math.floor((Date.now() - stat.mtimeMs) / (1000 * 60 * 60 * 24));
        info.fileExpiring = info.fileAgeDays > 12;
        info.fileSize = stat.size;
      } catch {}
    }
    res.json(info);
  });
}
