/**
 * v2 admin capture — stricter redaction and deeper navigation.
 *
 * - Masks customer domains (anything not in the brand allowlist).
 * - Masks customer-shaped usernames (anything not in the system allowlist).
 * - Masks input value attributes (the v1 only walked text nodes).
 * - Captures Settings sub-tabs (System, Services, Mail, DNS, Network & SSL,
 *   Security) since those are where LiteLLM URL / model / key likely live.
 *
 * Read-only. Never clicks save/apply/restart/delete.
 */
import { chromium, type Page } from 'playwright';
import { mkdir } from 'node:fs/promises';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const OUT_DIR = resolve(__dirname, '../../src/assets/screenshots/whp');

function need(name: string): string {
  const v = process.env[name];
  if (!v) throw new Error(`missing env: ${name}`);
  return v;
}

const BASE = need('WHP_BASE');
const USER = need('WHP_ADMIN_USER');
const PASS = need('WHP_ADMIN_PASS');

const HIDE_CSS = `.navbar-text, .brand-full { visibility: hidden !important; }`;

async function login(page: Page) {
  await page.goto(`${BASE}/login.php`, { waitUntil: 'domcontentloaded' });
  await page.fill('input[name="user"]', USER);
  await page.fill('input[name="password"]', PASS);
  await page.click('button[type="submit"]');
  await page.waitForLoadState('networkidle');
}

async function redact(page: Page) {
  await page.addStyleTag({ content: HIDE_CSS });

  await page.evaluate(() => {
    // ---- Mask all secret-shaped input values ----
    const secretInputSel = [
      'input[type="password"]',
      'input[name*="key" i]',
      'input[name*="token" i]',
      'input[name*="secret" i]',
      'input[name*="api" i]',
    ];
    for (const sel of secretInputSel) {
      document.querySelectorAll<HTMLInputElement>(sel).forEach((el) => {
        if (el.value) el.value = '████████████████';
      });
    }

    // ---- Brand allowlist ----
    const BRAND_SUFFIXES = [
      'anhonesthost.com', 'anhonesthost.net', 'anhonesthost.io',
      'anhh.co',
      'cloud-hosting.io',
      'example.com', 'example.org', 'example.net',
    ];

    // ---- System users to keep visible (others get masked) ----
    const SYSTEM_USERS_ARR = ['root', 'admin', 'whp', 'haproxy', 'apache', 'nginx', 'newuser'];

    // ---- Text-node swaps ----
    const swaps: [RegExp, string][] = [
      // Server / mail / nameserver hostnames in our infra
      [/whp\d+(-[a-z0-9]+)?\.cloud-hosting\.io/gi, '<your-server>.cloud-hosting.io'],
      [/mail\d+\.cloud-hosting\.io/gi, '<your-mail-server>.cloud-hosting.io'],
      [/whp\d+(-[a-z0-9]+)?\b/gi, '<your-server>'],
      [/WHP\d+(-[A-Z0-9]+)?\b/g, '<YOUR-SERVER>'],
      // IPs
      [/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, '<server-IP>'],
      // Home dirs
      [/\/docker\/users\/[a-z0-9-]+/g, '/docker/users/<user>'],
      // Common secret shapes
      [/sk-[A-Za-z0-9_-]{20,}/g, '<API-KEY>'],
      [/sk_(test|live)_[A-Za-z0-9]{20,}/g, '<API-KEY>'],
      [/Bearer\s+[A-Za-z0-9._-]{20,}/g, 'Bearer <API-KEY>'],
      [/eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}/g, '<JWT>'],
      // AI provider URLs
      [/https?:\/\/[^\s"'<>]*litellm[^\s"'<>]*/gi, '<litellm-endpoint>'],
      [/https?:\/\/[^\s"'<>]*\.anhonesthost\.(net|com|io)[^\s"'<>]*/gi, '<internal-endpoint>'],
      // Model family identifiers
      [/(claude|gpt|llama|mistral|gemini)[a-z0-9._-]*-\d[a-z0-9.\-]*/gi, '<model-name>'],
      // Bichon / Coraza / haproxy internal endpoints
      [/https?:\/\/[^\s"'<>]*\b(bichon|coraza-spoa|haproxy-manager)[^\s"'<>]*/gi, '<internal-service>'],
      // root → admin (we don't expose which UNIX user has super admin)
      [/Welcome, root\b/g, 'Welcome, admin'],
      [/(User:\s*)root\b/g, '$1admin'],
      [/(Home Directory:\s*)\/root\b/g, '$1/<admin-home>'],
      // Standalone 'root' (whole-word, not preceded by / or . — so paths like
      // /root/foo and references like .root stay untouched).
      [/(^|[^/.\w])root\b/g, '$1admin'],
    ];

    // ---- Walk text nodes ----
    const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
    const nodes: Text[] = [];
    let n: Node | null = walker.nextNode();
    while (n) { nodes.push(n as Text); n = walker.nextNode(); }
    for (const node of nodes) {
      let v = node.nodeValue ?? '';

      // Mask non-brand domain names (basic pattern: word.tld)
      v = v.replace(/\b([a-z0-9][a-z0-9-]{0,62}\.)+[a-z]{2,24}\b/gi, function (m) {
        const h = m.toLowerCase();
        let brand = false;
        for (const s of BRAND_SUFFIXES) { if (h === s || h.endsWith('.' + s)) { brand = true; break; } }
        return brand ? m : '<customer-domain>';
      });

      // Apply other swaps
      for (const [re, replacement] of swaps) v = v.replace(re, replacement);

      if (v !== node.nodeValue) node.nodeValue = v;
    }

    // ---- Mask sensitive content in input values ----
    document.querySelectorAll<HTMLInputElement>('input[type="text"], input[type="url"], input[type="email"], input:not([type])').forEach((el) => {
      if (!el.value) return;
      const v = el.value;
      let nv = v.replace(/\b([a-z0-9][a-z0-9-]{0,62}\.)+[a-z]{2,24}\b/gi, function (m) {
        const h = m.toLowerCase();
        let brand = false;
        for (const s of BRAND_SUFFIXES) { if (h === s || h.endsWith('.' + s)) { brand = true; break; } }
        return brand ? m : '<customer-domain>';
      });
      // Server / mail / NS hostnames
      nv = nv.replace(/whp\d+(-[a-z0-9]+)?\.cloud-hosting\.io/gi, '<your-server>.cloud-hosting.io');
      nv = nv.replace(/mail\d+\.cloud-hosting\.io/gi, '<your-mail-server>.cloud-hosting.io');
      nv = nv.replace(/ns[12]\.whp\d+\.cloud-hosting\.io/gi, 'ns<n>.<your-server>.cloud-hosting.io');
      // IPv4 — skip the well-known public resolvers / RFC1918 examples
      nv = nv.replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, function (ip) {
        const pub = new Set(['1.1.1.1', '1.0.0.1', '8.8.8.8', '8.8.4.4', '9.9.9.9', '149.112.112.112', '208.67.222.222', '208.67.220.220']);
        if (pub.has(ip)) return ip;
        if (/^(10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/.test(ip)) return ip; // private nets, fine
        return '<server-IP>';
      });
      if (nv !== v) el.value = nv;
    });

    // ---- Mask customer usernames in table cells ----
    // Heuristic: find table cells under a header containing 'user', 'username', or 'target' (case-insensitive)
    document.querySelectorAll('table').forEach((tbl) => {
      const headers = Array.from(tbl.querySelectorAll('thead th, thead td')).map(th => (th.textContent || '').trim().toLowerCase());
      const userCols: number[] = [];
      for (let i = 0; i < headers.length; i++) {
        if (/^(user(name)?|target|owner|account)$/.test(headers[i])) userCols.push(i);
      }
      if (userCols.length === 0) return;
      const rows = tbl.querySelectorAll('tbody tr');
      for (let r = 0; r < rows.length; r++) {
        const cells = rows[r].querySelectorAll('td');
        for (const idx of userCols) {
          const cell = cells[idx];
          if (!cell) continue;
          const txt = (cell.textContent || '').trim();
          if (!txt) continue;
          let isSystem = false;
          for (const u of SYSTEM_USERS_ARR) { if (u === txt) { isSystem = true; break; } }
          if (!isSystem) cell.textContent = '<user>';
        }
      }
    });
  });
}

async function shot(page: Page, id: string) {
  await page.waitForLoadState('networkidle');
  await page.waitForTimeout(500);
  await redact(page);
  const path = resolve(OUT_DIR, `${id}.png`);
  await page.screenshot({ path, fullPage: false });
  console.log(`captured ${id}`);
}

async function main() {
  await mkdir(OUT_DIR, { recursive: true });
  const browser = await chromium.launch({ headless: true });
  const ctx = await browser.newContext({ ignoreHTTPSErrors: true, viewport: { width: 1440, height: 900 }, deviceScaleFactor: 2 });
  const page = await ctx.newPage();
  try {
    await login(page);

    // Server Settings — click each tab
    await page.goto(`${BASE}/index.php?page=server-settings`);
    await page.waitForLoadState('networkidle');
    await shot(page, 'admin-srvset-system');
    for (const tab of ['services', 'mail', 'dns', 'network', 'security']) {
      const trigger = page.locator(`[data-bs-target="#tab-${tab}"]`).first();
      if (await trigger.count() === 0) { console.log('tab trigger not found:', tab); continue; }
      await trigger.click().catch(()=>{});
      await page.waitForTimeout(1200);
      await shot(page, `admin-srvset-${tab}`);
    }

    // Re-shoot the previously PII-heavy pages with v2 redaction
    const rerun = [
      { id: 'admin-coraza',          path: '/index.php?page=coraza-rules' },
      { id: 'admin-monitor-admin',   path: '/index.php?page=ai-monitor' },
      { id: 'admin-ignore-rules',    path: '/index.php?page=ai-monitor-ignore-rules' },
      { id: 'admin-user-mgmt',       path: '/index.php?page=user-management' },
      { id: 'admin-user-resources',  path: '/index.php?page=user-resources' },
      { id: 'admin-issues',          path: '/index.php?page=issues' },
      { id: 'admin-suspensions',     path: '/index.php?page=account-suspensions' },
      { id: 'admin-disk-usage',      path: '/index.php?page=disk-usage' },
      { id: 'admin-docker',          path: '/index.php?page=docker-management' },
      { id: 'admin-valkey',          path: '/index.php?page=valkey-admin' },
      { id: 'admin-updates',         path: '/index.php?page=update-management' },
      { id: 'admin-container-boot',  path: '/index.php?page=container-boot' },
      { id: 'admin-site-audit',      path: '/index.php?page=site-audit' },
      { id: 'admin-delegated',       path: '/index.php?page=delegated-users' },
    ];
    for (const r of rerun) {
      await page.goto(`${BASE}${r.path}`);
      await page.waitForLoadState('networkidle');
      await shot(page, r.id);
    }
  } finally {
    await browser.close();
  }
}

main().catch(e => { console.error(e); process.exit(1); });
