import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { getMnoteRuntimeConfig } from "@/lib/runtime-config";
import { resolveOnlyOfficeInternalUrl } from "@/lib/onlyoffice/internal-url";
import { gzipSync } from "node:zlib";

export const dynamic = "force-dynamic";
export const runtime = "nodejs";

const SERVICE_WORKER_SAFE_PATCH_SNIPPET = `
<script>
// 说明：
// - ONLYOFFICE 默认会尝试注册 document_editor_service_worker.js 用于静态资源缓存（这对二次打开速度很关键）。
// - 但在部分隧道/证书环境下，浏览器会阻止 ServiceWorker（例如证书不被信任导致 isSecureContext=false），
//   失败后会在控制台反复报错，甚至可能触发上层框架的全局错误捕获导致白屏。
// 这里做“安全兜底补丁”：
// - 若 ServiceWorker 可正常注册：不拦截（保留 ONLYOFFICE 自带缓存能力）
// - 若注册失败：吞掉错误并返回一个 fake registration，避免反复报错/白屏
window.__MNOTE_ONLYOFFICE_SW_PATCH__ = true;
(function () {
  try {
    if (!("serviceWorker" in navigator) || !navigator.serviceWorker) return;
    var fakeReg = {
      scope: location.origin + "/",
      update: function () { return Promise.resolve(); },
      unregister: function () { return Promise.resolve(true); }
    };
    var sw = navigator.serviceWorker;
    var origRegister = null;
    try { origRegister = sw.register && sw.register.bind(sw); } catch (e) {}
    var safeRegister = function () {
      try {
        if (!origRegister) return Promise.resolve(fakeReg);
        var p = origRegister.apply(sw, arguments);
        return Promise.resolve(p).catch(function () { return fakeReg; });
      } catch (e) {
        return Promise.resolve(fakeReg);
      }
    };
    try { sw.register = safeRegister; } catch (e) {}
    try {
      var proto = Object.getPrototypeOf(sw);
      if (proto && proto.register) proto.register = safeRegister;
    } catch (e) {}
  } catch (e) {}
})();
</script>
`.trim();

const XHR_REWRITE_SNIPPET = `
<script>
// 说明：ONLYOFFICE 在被反向代理（/onlyoffice-server）时，运行期仍可能发起指向内部端口
// http://127.0.0.1:8081/cache/... 的绝对请求（来自 ONLYOFFICE 内部逻辑）。
// 这会导致浏览器从 origin(3000) 跨域请求 8081 并触发 CORS 拦截。
// 这里在 ONLYOFFICE 页面（含其 iframe）内统一重写 XHR/fetch/window.open/location：
// 把内部 8081 的请求改写回同源 /onlyoffice-server/*。
window.__MNOTE_ONLYOFFICE_XHR_REWRITE__ = true;
(function () {
  try {
    var proxyPrefix = location.origin.replace(/\\/+$/, '') + '/onlyoffice-server';
    var internal = {
      'http://127.0.0.1:8081': true,
      'http://localhost:8081': true,
      'http://127.0.0.1:8082': true,
      'http://localhost:8082': true,
      // 说明：同上，兜底错误的 https://127.0.0.1:8081
      'https://127.0.0.1:8081': true,
      'https://localhost:8081': true,
      'https://127.0.0.1:8082': true,
      'https://localhost:8082': true
    };
    function rewrite(u) {
      try {
        var abs = new URL(u, location.origin);
        var origin = abs.protocol + '//' + abs.host;
        // 说明：外网 https 访问时，如果 ONLYOFFICE 错误生成了 http://<host>/onlyoffice-server/...，
        // 浏览器会以 Mixed Content 阻止请求；这里直接升级为 https。
        if (location.protocol === 'https:' && abs.protocol === 'http:' && abs.host === location.host) {
          if (abs.pathname === '/onlyoffice-server' || abs.pathname.indexOf('/onlyoffice-server/') === 0) {
            abs.protocol = 'https:';
            return abs.toString();
          }
        }
        if (!internal[origin]) return u;
        return proxyPrefix + abs.pathname + abs.search + abs.hash;
      } catch (e) {
        return u;
      }
    }

    try {
      var origOpen = XMLHttpRequest.prototype.open;
      XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
        return origOpen.call(this, method, rewrite(url), async, user, password);
      };
    } catch (e) {}

    try {
      var origFetch = window.fetch;
      if (typeof origFetch === 'function') {
        window.fetch = function (input, init) {
          try {
            if (typeof input === 'string') return origFetch.call(this, rewrite(input), init);
            if (input && typeof input === 'object' && typeof input.url === 'string') {
              return origFetch.call(this, new Request(rewrite(input.url), input), init);
            }
          } catch (e) {}
          return origFetch.call(this, input, init);
        };
      }
    } catch (e) {}

    try {
      var origWinOpen = window.open;
      if (typeof origWinOpen === 'function') {
        window.open = function (url, target, features) {
          try {
            if (typeof url === 'string') url = rewrite(url);
          } catch (e) {}
          return origWinOpen.call(this, url, target, features);
        };
      }
    } catch (e) {}

    try {
      var origAssign = location.assign && location.assign.bind(location);
      if (origAssign) location.assign = function (url) { return origAssign(rewrite(url)); };
      var origReplace = location.replace && location.replace.bind(location);
      if (origReplace) location.replace = function (url) { return origReplace(rewrite(url)); };
    } catch (e) {}
  } catch (e) {}
})();
</script>
`.trim();

const stripHopByHopHeaders = (headers: Headers) => {
  // 说明：Hop-by-hop headers 不应被代理转发/透传
  const hopByHop = [
    "connection",
    "keep-alive",
    "proxy-authenticate",
    "proxy-authorization",
    "te",
    "trailer",
    "transfer-encoding",
    "upgrade",
  ];
  for (const key of hopByHop) {
    headers.delete(key);
  }
};

const injectHtmlFixups = (html: string) => {
  // 说明：某些网络/DNS 环境下无法解析 `static.cloudflareinsights.com`，
  // ONLYOFFICE 页面若被注入了 Cloudflare 的 beacon 脚本，会在控制台反复报错，
  // 并可能因为 DNS 超时而拖慢首次加载。这里在反代层面做一次轻量清理。
  const cleaned = html
    // 说明：移除 Cloudflare Insights/Web Analytics beacon
    .replace(
      /<script[^>]*\bsrc=(["'])https?:\/\/static\.cloudflareinsights\.com\/beacon\.min\.js[^>]*>\s*<\/script>/gi,
      "",
    )
    // 说明：部分场景会有 `cdn-cgi` 相关注入（如 rocket-loader），同样会指向 Cloudflare 域名
    .replace(/<script[^>]*\bsrc=(["'])\/cdn-cgi\/[^>]*>\s*<\/script>/gi, "");

  // 说明：只注入一次，避免重复拼接
  const hasSw = cleaned.includes("window.__MNOTE_ONLYOFFICE_SW_PATCH__");
  const hasXhr = cleaned.includes("window.__MNOTE_ONLYOFFICE_XHR_REWRITE__");
  if (hasSw && hasXhr) return cleaned;
  const injected = [
    hasSw ? "" : SERVICE_WORKER_SAFE_PATCH_SNIPPET,
    hasXhr ? "" : XHR_REWRITE_SNIPPET,
  ]
    .filter(Boolean)
    .join("\n");
  return cleaned.replace(/<head[^>]*>/i, (m) => `${m}\n${injected}\n`);
};

const pickCacheControl = (pathParts: string[], contentType: string, method: string) => {
  const m = String(method || "").toUpperCase();
  if (m !== "GET" && m !== "HEAD") return "no-store";
  if (contentType.includes("text/html")) return "no-store";

  const p = `/${(pathParts ?? []).join("/")}`;
  const lower = p.toLowerCase();

  // 说明：这些路径通常是运行期接口/动态响应，不应缓存。
  if (
    lower.includes("/docservice/") ||
    lower.includes("/coauthoring/") ||
    lower.includes("/converter/") ||
    lower.includes("/healthcheck") ||
    lower.includes("/metrics")
  ) {
    return "no-store";
  }

  // 说明：/cache 下是 ONLYOFFICE 运行期二进制缓存（例如 Editor.bin、字体包、拼写词典等），
  // 通常以内容 ID 命名（可能没有扩展名），但内容在同一路径下应保持稳定。
  // 对这类资源做较长缓存可以显著提升二次打开速度。
  if (lower.includes("/cache/")) {
    return "public, max-age=604800, stale-while-revalidate=86400";
  }

  const isStaticByPath =
    lower.includes("/web-apps/") ||
    lower.includes("/sdkjs/") ||
    lower.includes("/fonts/") ||
    lower.endsWith(".js") ||
    lower.endsWith(".css") ||
    lower.endsWith(".map") ||
    lower.endsWith(".png") ||
    lower.endsWith(".jpg") ||
    lower.endsWith(".jpeg") ||
    lower.endsWith(".gif") ||
    lower.endsWith(".svg") ||
    lower.endsWith(".ico") ||
    lower.endsWith(".woff") ||
    lower.endsWith(".woff2") ||
    lower.endsWith(".ttf") ||
    lower.endsWith(".otf") ||
    lower.endsWith(".json") ||
    lower.endsWith(".wasm") ||
    lower.endsWith(".bin");

  // 说明：ONLYOFFICE 静态资源体积大，且文件名通常稳定；这里尽量给浏览器缓存，提升二次打开速度。
  if (isStaticByPath) {
    return "public, max-age=604800, stale-while-revalidate=86400, immutable";
  }

  return "public, max-age=300, stale-while-revalidate=300";
};

const buildStableEtag = (pathParts: string[], search: string) => {
  // 说明：ONLYOFFICE 的静态资源路径通常包含版本号/哈希（例如 /9.2.1-<hash>/sdkjs/...）。
  // 在 frp/隧道场景下，如果浏览器端请求带 `cache-control: no-cache`，会强制走 revalidate；
  // 但只要我们提供稳定 ETag，就可以快速返回 304，避免重复下载几十 MB 的资源。
  const p = `/${(pathParts ?? []).join("/")}`;
  const s = String(search || "");
  return `W/"mnote-oo:${p}${s}"`;
};

const isStaticCacheControl = (cc: string) => {
  const v = String(cc || "");
  // 与 pickCacheControl 的“静态资源”分支对齐
  return v.includes("max-age=604800");
};

const shouldGzip = (request: NextRequest, contentType: string) => {
  // 说明：在 TCP 隧道/跨地域场景下，静态资源体积大时开启 gzip 能显著降低首开耗时。
  // 仅对典型文本资源启用，避免对 woff2/wasm/bin 等已压缩或不适合压缩的内容浪费 CPU。
  const ae = String(request.headers.get("accept-encoding") || "").toLowerCase();
  if (!ae.includes("gzip")) return false;

  const ct = String(contentType || "").toLowerCase();
  const compressible =
    ct.startsWith("text/") ||
    ct.includes("javascript") ||
    ct.includes("json") ||
    ct.includes("xml") ||
    ct.includes("svg");

  return compressible;
};

const proxy = async (request: NextRequest, pathParts: string[]) => {
  const onlyofficeInternalUrl = await resolveOnlyOfficeInternalUrl();
  const incomingUrl = new URL(request.url);
  const target = new URL(`${onlyofficeInternalUrl}/${pathParts.map(encodeURIComponent).join("/")}`);
  target.search = incomingUrl.search;

  const headers = new Headers(request.headers);
  // 说明：避免把外部 Host 传给上游
  headers.delete("host");
  // 说明：ONLYOFFICE 在被反向代理时，会根据 X-Forwarded-* 推导自身对外地址，
  // 用于生成静态资源/缓存文件的 URL。若缺失这些信息，可能会返回指向内部端口
  //（例如 http://127.0.0.1:8081/cache/...）的绝对 URL，导致浏览器跨域请求被 CORS 拦截。
  const runtimeCfg = getMnoteRuntimeConfig();
  const xfp = (request.headers.get("x-forwarded-proto") || "").split(",")[0].trim();
  const xfh = (request.headers.get("x-forwarded-host") || "").split(",")[0].trim();
  const xfpPort = (request.headers.get("x-forwarded-port") || "").split(",")[0].trim();

  const originLike =
    request.headers.get("origin") ||
    request.headers.get("referer") ||
    "";
  const originUrl = (() => {
    try {
      if (!originLike) return null;
      return new URL(originLike);
    } catch {
      return null;
    }
  })();

  const cloudflare = (() => {
    try {
      const raw = String(runtimeCfg.cloudflareAppOrigin || "").trim();
      if (!raw) return null;
      return new URL(raw);
    } catch {
      return null;
    }
  })();

  const inferredProto =
    xfp ||
    (originUrl ? originUrl.protocol.replace(":", "") : "") ||
    (cloudflare && cloudflare.host === incomingUrl.host ? cloudflare.protocol.replace(":", "") : "") ||
    incomingUrl.protocol.replace(":", "");
  const inferredHost =
    xfh ||
    (originUrl ? originUrl.host : "") ||
    incomingUrl.host;
  const inferredPort =
    xfpPort ||
    (originUrl ? originUrl.port : "") ||
    (cloudflare && cloudflare.host === incomingUrl.host ? cloudflare.port : "") ||
    incomingUrl.port ||
    (inferredProto === "https" ? "443" : "80");

  const proto = inferredProto || "http";
  const host = inferredHost || incomingUrl.host;
  const port = inferredPort || (proto === "https" ? "443" : "80");

  headers.set("x-forwarded-host", host);
  headers.set("x-forwarded-proto", proto);
  headers.set("x-forwarded-port", port);
  headers.set("x-forwarded-prefix", "/onlyoffice-server");
  // 说明：让上游返回明文（identity），我们在此处按需 gzip 压缩，保证 content-encoding 与 body 一致。
  // 避免 Node fetch 自动解压后仍携带 content-encoding，导致浏览器报 ERR_CONTENT_DECODING_FAILED。
  headers.set("accept-encoding", "identity");
  stripHopByHopHeaders(headers);

  const method = request.method.toUpperCase();
  const hasBody = !["GET", "HEAD"].includes(method);

  // 说明：在 Node 的 fetch（Undici）里，ReadableStream body 需要设置 duplex: "half"。
  // 但 TS 的 RequestInit 类型不包含 duplex，这里用 any 兜底。
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const init: any = {
    method,
    headers,
    body: hasBody ? request.body : undefined,
    duplex: "half",
    redirect: "follow",
  };
  const upstream = await fetch(target, init);

  const upstreamContentLength = upstream.headers.get("content-length");
  const outHeaders = new Headers(upstream.headers);
  stripHopByHopHeaders(outHeaders);
  outHeaders.delete("content-encoding");
  outHeaders.delete("content-length");

  const contentType = upstream.headers.get("content-type") || "";
  const cacheControl = pickCacheControl(pathParts, contentType, request.method);
  outHeaders.set("cache-control", cacheControl);
  outHeaders.delete("pragma");
  outHeaders.delete("expires");
  outHeaders.delete("set-cookie");

  // 说明：为静态资源提供稳定 ETag，并支持 if-none-match → 304，提升重复打开速度。
  // 注意：仅对我们判定为“强缓存静态资源”的请求启用，避免影响协同接口/动态响应。
  if (isStaticCacheControl(cacheControl)) {
    const etag = buildStableEtag(pathParts, incomingUrl.search);
    outHeaders.set("etag", etag);
    const inm = String(request.headers.get("if-none-match") || "").trim();
    if (inm && (inm === etag || inm.split(",").map((s) => s.trim()).includes(etag))) {
      return new NextResponse(null, { status: 304, headers: outHeaders });
    }
  }

  // 说明：在部分隧道/反代场景下，浏览器对 “chunked” 的大静态资源不会写入磁盘缓存，
  // 导致重复打开仍然完整下载（几十 MB）。这里对“强缓存静态资源”改为定长响应：
  // - 先把上游 body 缓冲到 ArrayBuffer
  // - 写入 content-length
  // 这样浏览器更容易落盘缓存，二次打开会明显提速。
  if (isStaticCacheControl(cacheControl) && !contentType.includes("text/html")) {
    outHeaders.delete("vary");
    outHeaders.set("vary", "Accept-Encoding");
    if (method === "HEAD") {
      if (upstreamContentLength) outHeaders.set("content-length", upstreamContentLength);
      return new NextResponse(null, { status: upstream.status, headers: outHeaders });
    }
    const buf = await upstream.arrayBuffer();

    // 说明：优先 gzip（如果客户端支持），可显著降低跨地域/隧道场景下首开耗时。
    if (shouldGzip(request, contentType)) {
      try {
        const gz = gzipSync(Buffer.from(buf), { level: 6 });
        outHeaders.set("content-encoding", "gzip");
        outHeaders.set("content-length", String(gz.byteLength));
        return new NextResponse(gz, { status: upstream.status, headers: outHeaders });
      } catch {
        // ignore，回退到未压缩
      }
    }

    outHeaders.set("content-length", String(buf.byteLength));
    return new NextResponse(buf, { status: upstream.status, headers: outHeaders });
  }

  if (contentType.includes("text/html")) {
    const html = await upstream.text();
    const injected = injectHtmlFixups(html);
    return new NextResponse(injected, {
      status: upstream.status,
      headers: outHeaders,
    });
  }

  return new NextResponse(upstream.body, {
    status: upstream.status,
    headers: outHeaders,
  });
};

type RouteCtx = { params: Promise<{ path: string[] }> };

export async function GET(request: NextRequest, ctx: RouteCtx) {
  const { path } = await ctx.params;
  return proxy(request, path ?? []);
}

export async function POST(request: NextRequest, ctx: RouteCtx) {
  const { path } = await ctx.params;
  return proxy(request, path ?? []);
}

export async function PUT(request: NextRequest, ctx: RouteCtx) {
  const { path } = await ctx.params;
  return proxy(request, path ?? []);
}

export async function PATCH(request: NextRequest, ctx: RouteCtx) {
  const { path } = await ctx.params;
  return proxy(request, path ?? []);
}

export async function DELETE(request: NextRequest, ctx: RouteCtx) {
  const { path } = await ctx.params;
  return proxy(request, path ?? []);
}

export async function HEAD(request: NextRequest, ctx: RouteCtx) {
  const { path } = await ctx.params;
  return proxy(request, path ?? []);
}

export async function OPTIONS(request: NextRequest, ctx: RouteCtx) {
  const { path } = await ctx.params;
  return proxy(request, path ?? []);
}
