/* Live Audit — REAL in-browser checks against the entered domain.
 *
 * Things that genuinely run against the network:
 *  - GET https://{domain}/robots.txt           (parsed)
 *  - GET https://{domain}/sitemap.xml          (URL count)
 *  - GET https://{domain}/                     (title, OG, description, JSON-LD types)
 *  - Wikidata search API                       (real CORS-friendly endpoint)
 *  - Claude completion via window.claude.complete (real LLM call)
 *
 * Cross-origin failures are caught and reported as "Unreachable from browser
 * (CORS)" rather than blocking the panel.
 */

const LC_FETCH_TIMEOUT_MS = 9000;
const CLAUDE_RECALL_TIMEOUT_MS = 16000;

function timed(promise, ms, msg = 'timeout') {
  return Promise.race([
    promise,
    new Promise((_, r) => setTimeout(() => r(new Error(msg)), ms)),
  ]);
}

function brandFromDomain(d) {
  return d
    .replace(/^https?:\/\//, '')
    .replace(/^www\./, '')
    .split('/')[0]
    .split('.')[0]
    .split(/[-_]/)
    .filter(Boolean)
    .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
    .join(' ');
}

function cleanDomain(d) {
  return d.replace(/^https?:\/\//, '').replace(/\/.*$/, '').trim();
}

// Server-proxied fetch via /api/fetch (managed mode), else direct, else public CORS proxy.
async function tryFetch(url, { json = false } = {}) {
  // 1) Server proxy — preferred in managed mode (we have api/fetch.js)
  if (typeof window !== 'undefined' && window.aisoEnabled && window.aisoEnabled()) {
    try {
      const base = window.AISO_API_BASE.replace(/\/$/, '');
      const r = await timed(fetch(`${base}/fetch?url=${encodeURIComponent(url)}`), 15000);
      if (r.ok) {
        const data = await r.json();
        if (data?.ok && data.body) {
          // Wrap into a Response-like for the rest of the file
          const fakeResponse = {
            ok: true,
            status: data.status || 200,
            text: async () => data.body,
            headers: { get: () => data.contentType || '' },
          };
          return { r: fakeResponse, via: 'server' };
        }
      }
    } catch (e) { /* fall through */ }
  }

  // 2) Direct
  const directErr = await (async () => {
    try {
      const r = await timed(fetch(url, { redirect: 'follow' }), LC_FETCH_TIMEOUT_MS);
      if (r.ok) return { r, via: 'direct' };
      return { err: new Error(`HTTP ${r.status}`) };
    } catch (e) {
      return { err: e };
    }
  })();
  if (directErr.r) return directErr;

  // 3) Public CORS proxies (last resort). Try several — corsproxy.io often 403s now,
  //    so fall through to allorigins/codetabs exactly like discovery does. Without this
  //    the live audit reported "unreachable" on sites that block our server IP even
  //    though discovery fetched them fine via a later proxy.
  const PROXIES = [
    (u) => 'https://corsproxy.io/?' + encodeURIComponent(u),
    (u) => 'https://api.allorigins.win/raw?url=' + encodeURIComponent(u),
    (u) => 'https://api.codetabs.com/v1/proxy/?quest=' + encodeURIComponent(u),
  ];
  let lastErr;
  for (const mk of PROXIES) {
    try {
      const r = await timed(fetch(mk(url)), LC_FETCH_TIMEOUT_MS);
      if (r.ok) return { r, via: 'proxy' };
      lastErr = new Error(`Proxy HTTP ${r.status}`);
    } catch (e) { lastErr = e; }
  }
  return { err: lastErr || new Error('all proxies failed') };
}

const CHECK_DEFS = [
  { id: 'robots',  label: 'Robots.txt',        desc: 'Tells AI crawlers what to access' },
  { id: 'sitemap', label: 'Sitemap.xml',       desc: 'Lists indexable pages' },
  { id: 'jsonld',  label: 'Structured data',   desc: 'schema.org JSON-LD on your homepage' },
  { id: 'meta',    label: 'Title & OG tags',   desc: 'How your homepage describes itself' },
  { id: 'wikidata',label: 'Wikidata entity',   desc: 'Presence in the open knowledge graph' },
  { id: 'claude',  label: 'Claude recall',     desc: 'Real prompt against Claude Haiku 4.5' },
];

async function runCheck(id, domain) {
  if (id === 'robots') {
    const got = await tryFetch(`https://${domain}/robots.txt`);
    if (got.err) return { ok: false, detail: 'Unreachable (CORS or 404)' };
    const text = await got.r.text();
    const isHtml = /^\s*<!?(doctype|html)/i.test(text);
    if (isHtml) return { ok: false, detail: 'No robots.txt — served the HTML 404 page', via: got.via };
    const lines = text.split('\n').map(l => l.trim()).filter(Boolean);
    const userAgents = lines.filter(l => /^user-agent:/i.test(l)).length;
    const hasSitemap = /sitemap:/i.test(text);
    const aiBots = ['GPTBot', 'ClaudeBot', 'PerplexityBot', 'CCBot', 'Google-Extended', 'OAI-SearchBot', 'anthropic-ai'];
    const mentionedAI = aiBots.filter(b => text.includes(b));
    return {
      ok: true,
      detail: `${lines.length} lines · ${userAgents} agent block${userAgents === 1 ? '' : 's'}${hasSitemap ? ' · sitemap ✓' : ' · no sitemap line'}${mentionedAI.length ? ` · names ${mentionedAI.join(', ')}` : ' · no AI-bot rules'}`,
      via: got.via,
      preview: text.slice(0, 320),
    };
  }

  if (id === 'sitemap') {
    const got = await tryFetch(`https://${domain}/sitemap.xml`);
    if (got.err) return { ok: false, detail: 'Unreachable (CORS or 404)' };
    const text = await got.r.text();
    if (/^\s*<!?(doctype|html)/i.test(text)) return { ok: false, detail: 'No sitemap.xml — got an HTML page' };
    const isIndex = /<sitemapindex/i.test(text);
    const urls = (text.match(/<loc>/gi) || []).length;
    return {
      ok: urls > 0,
      detail: isIndex
        ? `Sitemap index · ${urls} sub-sitemap${urls === 1 ? '' : 's'}`
        : `${urls} URL${urls === 1 ? '' : 's'} listed`,
      via: got.via,
    };
  }

  if (id === 'jsonld' || id === 'meta') {
    // share a single homepage fetch via window cache
    window.__aisoHpCache = window.__aisoHpCache || {};
    if (!window.__aisoHpCache[domain]) {
      window.__aisoHpCache[domain] = (async () => {
        const got = await tryFetch(`https://${domain}/`);
        if (got.err) return { err: got.err.message || 'Unreachable' };
        const html = await got.r.text();
        return { html, via: got.via };
      })();
    }
    const cached = await window.__aisoHpCache[domain];
    if (cached.err) return { ok: false, detail: 'Couldn’t fetch homepage (likely CORS-blocked)' };

    if (id === 'meta') {
      const html = cached.html;
      const title = ((html.match(/<title[^>]*>([\s\S]*?)<\/title>/i) || [])[1] || '').trim();
      const og = ((html.match(/<meta[^>]+og:title[^>]+content=["']([^"']+)/i) || [])[1] || '').trim();
      const desc = ((html.match(/<meta[^>]+name=["']description["'][^>]+content=["']([^"']+)/i) || [])[1] || '').trim();
      const len = html.length;
      const ok = title.length > 0;
      return {
        ok,
        detail: title
          ? `“${title.length > 60 ? title.slice(0, 60) + '…' : title}”${og ? ' · OG ✓' : ' · no OG'}${desc ? ' · description ✓' : ' · no description'} · ${(len / 1024).toFixed(0)}kb`
          : 'No <title> on homepage',
        via: cached.via,
      };
    }

    // jsonld
    const html = cached.html;
    const blocks = html.match(/<script[^>]+type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi) || [];
    if (blocks.length === 0) return { ok: false, detail: 'No JSON-LD blocks found on homepage', via: cached.via };
    const types = new Set();
    blocks.forEach((b) => {
      const matches = b.match(/"@type"\s*:\s*"([^"]+)"/g) || [];
      matches.forEach((x) => {
        const t = x.match(/"([^"]+)"$/)[1];
        types.add(t);
      });
    });
    return {
      ok: true,
      detail: `${blocks.length} JSON-LD block${blocks.length > 1 ? 's' : ''} · ${[...types].join(', ') || 'no @type fields'}`,
      via: cached.via,
    };
  }

  if (id === 'wikidata') {
    const brand = brandFromDomain(domain);
    try {
      // Use discover.jsx's filtered lookup if available (drops false matches
      // like "American record producer" for unrelated brand names).
      if (typeof window.lookupWikidata === 'function') {
        const wd = await timed(window.lookupWikidata(brand), LC_FETCH_TIMEOUT_MS);
        if (wd) {
          return {
            ok: true,
            detail: `${wd.id} — ${wd.description || wd.label}`,
          };
        }
        return { ok: false, detail: `No matching Wikidata entity for “${brand}”` };
      }
      // Fallback (shouldn't normally hit this — discover.jsx loads first)
      const r = await timed(
        fetch(`https://www.wikidata.org/w/api.php?action=wbsearchentities&search=${encodeURIComponent(brand)}&language=en&format=json&origin=*`),
        LC_FETCH_TIMEOUT_MS
      );
      const data = await r.json();
      if (data.search && data.search.length > 0) {
        const top = data.search[0];
        return {
          ok: true,
          detail: `${top.id} — ${top.description || top.label}`,
        };
      }
      return { ok: false, detail: `No Wikidata entity matches “${brand}”` };
    } catch (e) {
      return { ok: false, detail: e.message || 'Wikidata error' };
    }
  }

  if (id === 'claude') {
    // Use callModel — transparently routes through the server in managed mode
    // (uses env-var ANTHROPIC_API_KEY) or falls back to BYO key / sandbox helper.
    const profile = (() => {
      try { return JSON.parse(localStorage.getItem('aiso:profile:v1') || '{}'); }
      catch { return {}; }
    })();
    const anthropicKey = profile?.keys?.anthropic || '';
    const managed = (window.getManagedProviders && window.getManagedProviders().has('anthropic')) || false;

    if (!anthropicKey && !managed && typeof window.claude?.complete !== 'function') {
      return { ok: false, detail: 'Claude unavailable — configure ANTHROPIC_API_KEY on the server, or wire a key in Settings' };
    }
    const brand = brandFromDomain(domain);
    const prompt = `Be brief and honest. Based ONLY on what you know from training (not browsing), in 2-3 short sentences:

1) Have you heard of "${brand}" (${domain})? Yes/no.
2) Roughly what vertical or industry is it?
3) If someone asked you for top recommendations in that vertical, would you naturally include it? Yes/no.

No preamble, just the assessment.`;
    try {
      let ans;
      let via = 'sandbox';
      if (typeof window.callModel === 'function' && (anthropicKey || managed)) {
        const r = await timed(window.callModel('anthropic', anthropicKey, prompt, { maxTokens: 300 }), CLAUDE_RECALL_TIMEOUT_MS);
        ans = r.text || '';
        via = managed && !anthropicKey ? 'server' : 'your-key';
      } else {
        ans = await timed(window.claude.complete(prompt), CLAUDE_RECALL_TIMEOUT_MS);
      }
      const lower = (ans || '').toLowerCase();
      const heard = !/(haven'?t heard|not familiar|don'?t (have any info|know|recognize)|no information|cannot find|unclear|not aware|i'?m not sure)/.test(lower);
      const recommends = /yes.*(recommend|include)|would (probably )?(include|mention|recommend)/.test(lower) || /1\).*yes[\s\S]*3\).*yes/.test(lower);
      const ok = heard && recommends;
      return {
        ok,
        detail: ans.trim(),
        isText: true,
        via,
      };
    } catch (e) {
      return { ok: false, detail: e.message || 'Claude call failed' };
    }
  }

  return { ok: false, detail: 'Unknown check' };
}

function CheckIcon({ status }) {
  if (status === 'pass') return (
    <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2.5 7.5l3 3 6-6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
  );
  if (status === 'fail') return (
    <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M3 3l8 8M11 3l-8 8" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/></svg>
  );
  if (status === 'running') return (
    <svg width="14" height="14" viewBox="0 0 14 14" fill="none" className="spin"><circle cx="7" cy="7" r="5" stroke="currentColor" strokeWidth="1.5" strokeOpacity="0.25"/><path d="M7 2a5 5 0 0 1 5 5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></svg>
  );
  return <span style={{ opacity: 0.4, fontFamily: 'var(--mono)', fontSize: 11 }}>idle</span>;
}

function LiveAudit({ domain }) {
  const cleaned = cleanDomain(domain || 'bekon.ai');
  const [state, setState] = React.useState({});
  const [running, setRunning] = React.useState(false);
  const [hasRun, setHasRun] = React.useState(false);
  const [expanded, setExpanded] = React.useState(null);

  function update(id, patch) {
    setState((s) => ({ ...s, [id]: { ...(s[id] || {}), ...patch } }));
  }

  async function run() {
    if (running) return;
    setRunning(true);
    setHasRun(true);
    delete window.__aisoHpCache?.[cleaned];
    setState({});

    await Promise.all(
      CHECK_DEFS.map(async (def, i) => {
        await new Promise((r) => setTimeout(r, i * 220));
        update(def.id, { status: 'running' });
        const t0 = performance.now();
        try {
          const result = await runCheck(def.id, cleaned);
          const elapsed = Math.round(performance.now() - t0);
          update(def.id, {
            status: result.ok ? 'pass' : 'fail',
            detail: result.detail,
            via: result.via,
            elapsed,
            preview: result.preview,
            isText: result.isText,
          });
        } catch (e) {
          const elapsed = Math.round(performance.now() - t0);
          update(def.id, { status: 'fail', detail: e.message || 'error', elapsed });
        }
      })
    );
    setRunning(false);
  }

  const summary = (() => {
    const vals = Object.values(state);
    const done = vals.filter((v) => v.status === 'pass' || v.status === 'fail').length;
    const passed = vals.filter((v) => v.status === 'pass').length;
    return { done, passed, total: CHECK_DEFS.length };
  })();

  return (
    <div className="live-audit">
      <div className="la-head">
        <div>
          <div className="eyebrow"><span className="pulse" /> Live · runs server-side, now</div>
          <h3>Verify against the real <span className="mono-domain">{cleaned}</span></h3>
          <p>
            Every check fetches your real domain through the AISO proxy — robots.txt, sitemap, JSON-LD, meta, Wikidata — and asks Claude (via your wired keys) whether it actually knows you. No mocks; results match what an AI assistant would see today.
          </p>
        </div>
        <div className="la-cta-col">
          <button className="btn btn-primary la-run" onClick={run} disabled={running}>
            {running ? (<><span className="dot-spin" /> Running…</>) : hasRun ? 'Run again' : 'Run live audit'}
          </button>
          {hasRun && (
            <div className="la-summary">
              <span className="num">{summary.passed}</span>
              <span className="sep">/</span>
              <span className="total">{summary.total}</span>
              <span className="label">passing</span>
            </div>
          )}
        </div>
      </div>

      <div className="check-grid">
        {CHECK_DEFS.map((def) => {
          const st = state[def.id] || { status: hasRun ? 'idle' : 'idle' };
          const isExp = expanded === def.id;
          const expandable = st.status === 'pass' || st.status === 'fail';
          return (
            <div
              key={def.id}
              className={`check check-${st.status} ${isExp ? 'is-exp' : ''}`}
              onClick={() => expandable && setExpanded(isExp ? null : def.id)}
              role={expandable ? 'button' : undefined}
            >
              <div className="check-top">
                <div className="check-name">
                  <span className="check-icon"><CheckIcon status={st.status} /></span>
                  {def.label}
                </div>
                {st.elapsed != null && <div className="check-time">{st.elapsed}ms{st.via === 'proxy' ? ' · proxy' : ''}</div>}
              </div>
              <div className="check-desc">{def.desc}</div>
              {st.detail && (
                <div className={`check-detail ${st.isText ? 'is-text' : ''}`}>
                  {st.detail}
                </div>
              )}
              {isExp && st.preview && (
                <pre className="check-preview">{st.preview}</pre>
              )}
            </div>
          );
        })}
      </div>

      <div className="la-foot">
        <span className="mono-tag">live</span>
        <span>Checks run server-side via the AISO proxy. Reads only — never any writes. Caches per-domain for ~10 minutes.</span>
      </div>
    </div>
  );
}

Object.assign(window, { LiveAudit, brandFromDomain, cleanDomain });
