/**
 * GET /api/preview/proxy?url=...&brandId=...
 *
 * Proxies a customer's website page so the Link Inspector can render
 * it in an iframe even when the site sets X-Frame-Options: SAMEORIGIN
 * or CSP frame-ancestors. The proxied response is served from the
 * meSEO origin without frame-blocking headers.
 *
 * Security: only proxies domains that belong to the authenticated
 * user's brand (verified via Brand.domain + TrackedSite.allowedDomains).
 */

import { NextResponse } from "next/server";
import { getCurrentUser } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import { APP_URL } from "@/lib/config";

export const dynamic = "force-dynamic";
export const maxDuration = 15;

export async function GET(req: Request) {
  const user = await getCurrentUser();
  if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

  const { searchParams } = new URL(req.url);
  const targetUrl = searchParams.get("url");
  const brandId = searchParams.get("brandId");

  if (!targetUrl || !brandId) {
    return NextResponse.json({ error: "url and brandId required" }, { status: 400 });
  }

  let parsedUrl: URL;
  try {
    parsedUrl = new URL(targetUrl);
  } catch {
    return NextResponse.json({ error: "Invalid URL" }, { status: 400 });
  }

  if (!["http:", "https:"].includes(parsedUrl.protocol)) {
    return NextResponse.json({ error: "Only HTTP(S) URLs allowed" }, { status: 400 });
  }

  const brand = await prisma.brand.findUnique({
    where: { id: brandId },
    include: {
      organization: { include: { memberships: true } },
      trackedSite: { select: { siteId: true, allowedDomains: true } },
    },
  });
  if (!brand || !brand.organization.memberships.some((m) => m.userId === user.id)) {
    return NextResponse.json({ error: "Access denied" }, { status: 403 });
  }

  const requestedHost = parsedUrl.hostname.replace(/^www\./, "");
  const brandDomain = (brand.domain || "").replace(/^www\./, "");
  const allowed = brand.trackedSite?.allowedDomains?.map((d) => d.replace(/^www\./, "")) ?? [];
  const allDomains = [brandDomain, ...allowed].filter(Boolean);

  if (!allDomains.some((d) => requestedHost === d || requestedHost.endsWith("." + d))) {
    return NextResponse.json({ error: "Domain not authorized for this brand" }, { status: 403 });
  }

  try {
    const upstream = await fetch(targetUrl, {
      headers: {
        "User-Agent": "Mozilla/5.0 (compatible; meSEO-Preview/1.0)",
        "Accept": "text/html,application/xhtml+xml",
      },
      redirect: "follow",
      signal: AbortSignal.timeout(10_000),
    });

    if (!upstream.ok) {
      return NextResponse.json(
        { error: `Upstream returned ${upstream.status}` },
        { status: 502 },
      );
    }

    const contentType = upstream.headers.get("content-type") || "";
    if (!contentType.includes("text/html")) {
      return new Response(upstream.body, {
        headers: { "Content-Type": contentType },
      });
    }

    let html = await upstream.text();
    const base = `${parsedUrl.protocol}//${parsedUrl.host}`;
    const appUrl = APP_URL;
    const tagSiteId = brand.trackedSite?.siteId ?? "";

    const previewConfig = [
      `<script>`,
      // Global suppress flag — survives meSEOConfig being overwritten
      // by the customer's own snippet later in the page
      `window.__meseo_suppress=true;`,
      // Set meSEOConfig so the Site Tag can resolve its origin
      `window.meSEOConfig=window.meSEOConfig||{};`,
      `window.meSEOConfig.siteId="${tagSiteId}";`,
      `window.meSEOConfig.appUrl="${appUrl}";`,
      `window.meSEOConfig.isPreview=true;`,
      `window.meSEOConfig.suppressEvents=true;`,
      // Block all event-sending fetch/XHR calls at the network layer
      // so the proxied page doesn't flood /api/* with 403s
      `(function(){`,
      `var B=["/api/collect","/api/site-tag/event","/api/site-tag/conversion",`,
      `"/api/site-tag/heatmap","/api/site-tag/rum","/api/site-tag/seo-monitor",`,
      `"/api/site-tag/nav-structure","/api/site-tag/page-seen",`,
      `"/api/track"];`,
      `var oF=window.fetch;`,
      `window.fetch=function(u,o){`,
      `var m=o&&o.method?o.method.toUpperCase():"GET";`,
      `if(m!=="GET"&&typeof u==="string"&&B.some(function(p){return u.indexOf(p)>-1}))`,
      `return Promise.resolve(new Response("{}",{status:200,headers:{"content-type":"application/json"}}));`,
      `return oF.apply(this,arguments);`,
      `};`,
      `var oO=XMLHttpRequest.prototype.open;`,
      `var oS=XMLHttpRequest.prototype.send;`,
      `XMLHttpRequest.prototype.open=function(m,u){`,
      `this._mBlock=typeof u==="string"&&B.some(function(p){return u.indexOf(p)>-1});`,
      `return oO.apply(this,arguments);`,
      `};`,
      `XMLHttpRequest.prototype.send=function(){`,
      `if(this._mBlock)return;`,
      `return oS.apply(this,arguments);`,
      `};`,
      `})();`,
      `</script>`,
    ].join("");

    // Inject config at TOP of <head> (suppress flag + fetch interceptor)
    // AND at BOTTOM of <head> (re-set appUrl after customer snippet
    // overwrites meSEOConfig). The customer's snippet typically does
    // window.meSEOConfig = { siteId: "..." } which wipes our appUrl.
    const lateConfig = [
      `<script>`,
      `window.meSEOConfig=window.meSEOConfig||{};`,
      `window.meSEOConfig.appUrl="${appUrl}";`,
      `window.meSEOConfig.siteId=window.meSEOConfig.siteId||"${tagSiteId}";`,
      `window.meSEOConfig.isPreview=true;`,
      `window.meSEOConfig.suppressEvents=true;`,
      `</script>`,
    ].join("");

    html = html.replace(/<head([^>]*)>/i, `<head$1>${previewConfig}<base href="${base}/">`);
    html = html.replace(/<\/head>/i, `${lateConfig}</head>`);

    // Fetch broken links server-side and inject the overlay directly
    // into the HTML. This bypasses t.js entirely — no cache dependency.
    // `base` is the live site origin so verification HEAD requests
    // resolve relative URLs against the real server, not the proxy.
    const overlayScript = await buildOverlayScript(brandId, parsedUrl.pathname, base);
    if (overlayScript) {
      html = html.replace(/<\/body>/i, `${overlayScript}</body>`);
    }

    return new Response(html, {
      headers: {
        "Content-Type": "text/html; charset=utf-8",
        "Cache-Control": "private, no-store",
      },
    });
  } catch (err) {
    const msg = err instanceof Error ? err.message : "Proxy fetch failed";
    return NextResponse.json({ error: msg }, { status: 502 });
  }
}

/**
 * Fetch broken links for the page and return a <script> that paints
 * red pulsing outlines + 404 badges on matching elements. Runs
 * entirely server-side — no dependency on t.js or its cache state.
 *
 * Verifies each stored "broken_internal_link" event against the live
 * site before painting, so resources that have since been fixed don't
 * surface as false positives on the overlay. The previous version
 * trusted SiteEvent records directly, which led to a fixed image
 * appearing flagged for weeks after the fix.
 */
async function buildOverlayScript(brandId: string, pagePath: string, pageOrigin: string): Promise<string | null> {
  try {
    const normalized = pagePath.replace(/\/+$/, "") || "/";
    const since = new Date(Date.now() - 30 * 86_400_000);
    const events = await prisma.siteEvent.findMany({
      where: {
        brandId,
        eventType: "broken_internal_link",
        pageUrl: normalized,
        timestamp: { gte: since },
      },
      orderBy: { timestamp: "desc" },
      select: { eventData: true },
      take: 100,
    });

    const seen = new Set<string>();
    const candidates: Array<{ dest: string; text: string; type: string; sel: string; status: number }> = [];
    for (const ev of events) {
      const d = (ev.eventData ?? {}) as Record<string, unknown>;
      const dest = (d.destinationUrl as string) || "";
      if (!dest || seen.has(dest)) continue;
      seen.add(dest);
      candidates.push({
        dest,
        text: (d.linkText as string) || "",
        type: (d.elementType as string) || "a",
        sel: (d.selector as string) || "",
        status: typeof d.statusCode === "number" ? d.statusCode : 404,
      });
    }

    if (candidates.length === 0) return null;

    // Verify each candidate is STILL broken with a HEAD request from
    // the server. This filters out the false positives where t.js
    // recorded a transient 404 during a deploy/glitch and the
    // resource is fine now. Capped at 8s total to avoid hanging the
    // proxy. Only resources that come back non-200 (or unreachable)
    // are painted.
    async function verify(rel: string): Promise<{ ok: boolean; status: number }> {
      try {
        const absolute = rel.startsWith("http") ? rel : new URL(rel, pageOrigin).href;
        const ctrl = new AbortController();
        const t = setTimeout(() => ctrl.abort(), 4000);
        const res = await fetch(absolute, {
          method: "HEAD",
          headers: {
            "User-Agent": "meSEO Link Inspector (verification)",
            "Referer": pageOrigin,
          },
          redirect: "follow",
          signal: ctrl.signal,
        });
        clearTimeout(t);
        return { ok: res.ok, status: res.status };
      } catch {
        // Network error — could be CORS, timeout, or DNS failure.
        // Treat as "couldn't verify"; we err on the side of NOT
        // flagging since the proxy origin may differ from the user's
        // browser origin and trigger spurious failures.
        return { ok: true, status: 0 };
      }
    }

    const verifications = await Promise.allSettled(
      candidates.map((c) => verify(c.dest)),
    );

    const links: typeof candidates = [];
    for (let i = 0; i < candidates.length; i++) {
      const v = verifications[i];
      if (v.status === "fulfilled" && !v.value.ok && v.value.status > 0) {
        // Confirmed still broken — update status to reflect the
        // verified live status code rather than the historical one.
        links.push({ ...candidates[i], status: v.value.status });
      }
    }

    if (links.length === 0) return null;

    const linksJson = JSON.stringify(links);
    return `
<style>
[data-meseo-broken]{outline:3px solid #EF4444!important;outline-offset:2px!important;animation:meseoPulse 2s infinite!important}
[data-meseo-focused="1"]{outline:3px dashed #EF4444!important;outline-offset:4px!important;animation:meseoFocus 1.2s ease-in-out 3!important;background-color:rgba(239,68,68,0.15)!important}
@keyframes meseoPulse{0%,100%{box-shadow:0 0 0 0 rgba(239,68,68,0.4)}50%{box-shadow:0 0 0 8px rgba(239,68,68,0)}}
@keyframes meseoFocus{0%,100%{box-shadow:0 0 0 0 rgba(239,68,68,0.75),0 0 40px rgba(239,68,68,0.6)}50%{box-shadow:0 0 0 14px rgba(239,68,68,0),0 0 60px rgba(239,68,68,0.2)}}
.__meseo_badge{position:absolute!important;background:#EF4444!important;color:#fff!important;font:600 10px/16px system-ui!important;padding:1px 6px!important;border-radius:10px!important;z-index:2147483646!important;pointer-events:none!important;box-shadow:0 2px 6px rgba(0,0,0,0.25)!important}
.__meseo_focus_label{position:absolute!important;background:#EF4444!important;color:#fff!important;font:600 11px/18px system-ui!important;padding:4px 10px!important;border-radius:6px!important;z-index:2147483647!important;pointer-events:none!important;box-shadow:0 6px 16px rgba(239,68,68,0.4)!important;max-width:420px!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important}
</style>
<script>
(function(){
  var links=${linksJson};
  var painted=[];
  var focusEl=null,focusLabel=null;
  function findEl(r){
    if(r.sel){try{var e=document.querySelector(r.sel);if(e)return e}catch(x){}}
    if(r.type==="img"){
      var imgs=document.querySelectorAll("img[src]");
      for(var i=0;i<imgs.length;i++){try{if(new URL(imgs[i].getAttribute("src"),location.href).pathname===r.dest)return imgs[i]}catch(x){}}
    }
    var as=document.querySelectorAll("a[href]");
    for(var j=0;j<as.length;j++){try{if(new URL(as[j].getAttribute("href"),location.href).pathname===r.dest)return as[j]}catch(x){}}
    return null;
  }
  function clearFocus(){
    if(focusEl)try{focusEl.removeAttribute("data-meseo-focused")}catch(x){}
    if(focusLabel&&focusLabel.parentNode)focusLabel.parentNode.removeChild(focusLabel);
    focusEl=null;focusLabel=null;
  }
  function focusOn(idx,record){
    clearFocus();
    var el=painted[idx]||findEl(record||links[idx]||{});
    if(!el)return;
    el.setAttribute("data-meseo-focused","1");
    focusEl=el;
    var r=record||links[idx]||{};
    var st=r.status||r.statusCode||404;
    var dest=r.dest||r.destinationUrl||"";
    var tp=r.type||r.elementType||"link";
    var tx=r.text||r.linkText||"";
    var label=document.createElement("span");
    label.className="__meseo_focus_label";
    label.textContent=tp==="img"
      ?"Broken image (HTTP "+st+") — "+(dest.split("/").pop()||dest)
      :"Broken link (HTTP "+st+") — \\u201c"+(tx||"link")+"\\u201d → "+dest;
    document.body.appendChild(label);
    focusLabel=label;
    var rect=el.getBoundingClientRect();
    var top=rect.top+window.scrollY-28;
    if(rect.top<40)top=rect.top+window.scrollY+rect.height+6;
    label.style.top=top+"px";
    label.style.left=(rect.left+window.scrollX)+"px";
    el.scrollIntoView({behavior:"smooth",block:"center"});
    setTimeout(function(){if(focusEl===el)try{el.removeAttribute("data-meseo-focused")}catch(x){}},3800);
  }
  function paint(){
    var first=null;
    for(var i=0;i<links.length;i++){
      var el=findEl(links[i]);
      if(!el)continue;
      el.setAttribute("data-meseo-broken",String(i));
      painted[i]=el;
      var b=document.createElement("span");
      b.className="__meseo_badge";
      b.textContent=String(links[i].status);
      var rect=el.getBoundingClientRect();
      b.style.top=(rect.top+window.scrollY-8)+"px";
      b.style.left=(rect.left+window.scrollX+rect.width-20)+"px";
      b.style.position="absolute";
      document.body.appendChild(b);
      if(!first)first=el;
    }
    if(first)try{first.scrollIntoView({behavior:"smooth",block:"center"})}catch(x){}
    if(window.parent!==window)try{window.parent.postMessage({type:"meseo-inspect-ready",brokenCount:links.length},"*")}catch(x){}
  }
  window.addEventListener("message",function(ev){
    if(!ev||!ev.data||typeof ev.data!=="object")return;
    if(ev.data.type==="meseo-inspect-focus"){
      var idx=typeof ev.data.index==="number"?ev.data.index:-1;
      focusOn(idx,ev.data.record||null);
    }else if(ev.data.type==="meseo-inspect-clear-focus"){
      clearFocus();
    }
  });
  if(document.readyState==="complete")setTimeout(paint,1000);
  else window.addEventListener("load",function(){setTimeout(paint,1000)});
})();
</script>`;
  } catch (err) {
    console.error("[proxy] buildOverlayScript failed:", err instanceof Error ? err.message : String(err));
    return null;
  }
}
