// credential_vault.js — HTTP Basic Auth credential vault for pup sessions.
//
// Why this exists
// ───────────────
// When a pup tab navigates to a host that issues an HTTP Basic Auth challenge
// (nxp.componentsearchengine.com, mouser.componentsearchengine.com, several
// vendor design portals), Chrome shows a NATIVE auth dialog. That dialog
// lives outside the page's DOM and CANNOT be driven via browser_eval / DOM
// manipulation. Without this vault, the user has to manually type the same
// email + password every single time. Users have called this "heinous."
//
// Puppeteer exposes `page.authenticate({username, password})`, which
// intercepts basic-auth challenges BEFORE the dialog shows and supplies
// stored creds programmatically. This module owns the storage + lookup
// half of that flow.
//
// Storage
// ───────
// Two parts:
//   1. The PASSWORD lives in the OS keychain via `keytar` (Windows DPAPI /
//      macOS Keychain / Linux libsecret). Service name `adom-desktop-pup`.
//      Account name is the host pattern. The OS encrypts at rest using the
//      logged-in-user's session key, so even disk theft can't extract creds.
//   2. The INDEX (host pattern + username + timestamps, NEVER passwords)
//      lives at `plugins/puppeteer/credentials.index.json` so we can list
//      entries fast without poking keytar 50 times.
//
// Host matching
// ─────────────
// Patterns support a simple glob:
//   "*.componentsearchengine.com"  → matches any subdomain (suffix match)
//   "componentsearchengine.com"    → exact match only
//   "secure.example.com"           → exact match only
// Best-match wins (longest non-glob suffix beats shorter glob).
// For a navigation to https://nxp.componentsearchengine.com/preview.php,
// host = "nxp.componentsearchengine.com" — would match "*.componentsearchengine.com".
//
// What this module does NOT do
// ────────────────────────────
//   - Ship its own crypto. Never. OS keychain only.
//   - Echo passwords back via list/get APIs. Index returns username + host only.
//   - Persist anything to the wiki / cloud. All local, all per-machine.

const fs = require('fs');
const path = require('path');

const KEYTAR_SERVICE = 'adom-desktop-pup';
const INDEX_PATH = path.join(__dirname, 'credentials.index.json');

let keytar = null;
let keytarLoadError = null;
try {
  // keytar is a native module; load lazily so the bridge starts even if it's
  // missing. The first credential_set call will surface a clear error.
  keytar = require('keytar');
} catch (e) {
  keytarLoadError = e.message;
  console.log(`[credential_vault] keytar load failed (will be required for credential_set): ${e.message}`);
}

// ── Index helpers ──────────────────────────────────────────────────────

function readIndex() {
  try {
    const raw = fs.readFileSync(INDEX_PATH, 'utf8');
    const parsed = JSON.parse(raw);
    if (Array.isArray(parsed.entries)) return parsed.entries;
  } catch {}
  return [];
}

function writeIndex(entries) {
  const data = { version: 1, entries };
  fs.writeFileSync(INDEX_PATH, JSON.stringify(data, null, 2));
}

// ── Glob matching ──────────────────────────────────────────────────────

function hostMatchesPattern(host, pattern) {
  if (!host || !pattern) return false;
  const h = host.toLowerCase();
  const p = pattern.toLowerCase();
  if (p === h) return true;
  if (p.startsWith('*.')) {
    const suffix = p.slice(1); // ".componentsearchengine.com"
    if (h === suffix.slice(1)) return true;     // bare apex is a valid match
    if (h.endsWith(suffix)) return true;
  }
  return false;
}

// "Best match" = longest non-glob suffix wins. Exact match beats glob match
// of the same length. So "nxp.componentsearchengine.com" entry would win
// over "*.componentsearchengine.com" entry for that exact host.
function scorePattern(pattern, host) {
  if (!hostMatchesPattern(host, pattern)) return -1;
  const exact = !pattern.startsWith('*.');
  return (exact ? 1000 : 0) + pattern.length;
}

// ── Public API ─────────────────────────────────────────────────────────

async function setCredential({ host, username, password }) {
  if (!host || !username || !password) {
    throw new Error('host, username, and password are all required');
  }
  if (!keytar) {
    throw new Error(
      `keytar is not installed in plugins/puppeteer/. Install: cd plugins/puppeteer && npm install keytar. Original load error: ${keytarLoadError}`
    );
  }
  await keytar.setPassword(KEYTAR_SERVICE, host, password);
  const entries = readIndex();
  const idx = entries.findIndex(e => e.host === host);
  const now = new Date().toISOString();
  if (idx >= 0) {
    entries[idx] = { ...entries[idx], username, updatedAt: now };
  } else {
    entries.push({ host, username, addedAt: now, updatedAt: now });
  }
  writeIndex(entries);
  return { ok: true, host, username };
}

async function getCredentialForUrl(url) {
  if (!keytar) return null;
  let host;
  try {
    host = new URL(url).hostname;
  } catch {
    return null;
  }
  const entries = readIndex();
  let best = null;
  let bestScore = -1;
  for (const e of entries) {
    const s = scorePattern(e.host, host);
    if (s > bestScore) {
      bestScore = s;
      best = e;
    }
  }
  if (!best) return null;
  const password = await keytar.getPassword(KEYTAR_SERVICE, best.host);
  if (!password) return null;
  return { host: best.host, username: best.username, password };
}

function listCredentials() {
  // Index already excludes passwords by construction
  return readIndex().map(e => ({
    host: e.host,
    username: e.username,
    addedAt: e.addedAt,
    updatedAt: e.updatedAt,
  }));
}

async function deleteCredential(host) {
  if (!host) throw new Error('host required');
  let removed = false;
  if (keytar) {
    try {
      removed = await keytar.deletePassword(KEYTAR_SERVICE, host);
    } catch (e) {
      console.log(`[credential_vault] keytar deletePassword error: ${e.message}`);
    }
  }
  const entries = readIndex();
  const before = entries.length;
  const next = entries.filter(e => e.host !== host);
  if (next.length !== before) {
    writeIndex(next);
    return { ok: true, host, removedFromKeychain: removed, removedFromIndex: true };
  }
  return { ok: true, host, removedFromKeychain: removed, removedFromIndex: false };
}

// Apply stored creds to a page given the target URL it's about to load.
// Idempotent: if no match, no-op. Returns the matched host pattern (or null).
async function applyCredentialsToPage(page, url) {
  const creds = await getCredentialForUrl(url);
  if (!creds) return null;
  try {
    await page.authenticate({ username: creds.username, password: creds.password });
    return creds.host;
  } catch (e) {
    console.log(`[credential_vault] page.authenticate failed for ${creds.host}: ${e.message}`);
    return null;
  }
}

module.exports = {
  setCredential,
  getCredentialForUrl,
  listCredentials,
  deleteCredential,
  applyCredentialsToPage,
  // Exposed for tests + diagnostics
  hostMatchesPattern,
  scorePattern,
};