/**
 * fetch-bridge · 转发端点(门控 → rewrite 注册表 dispatch → 双向流式转发,
 * Req 1.1, 1.3, 1.5, 2.1–2.10, 7.1, 7.2)。
 *
 * `createFetchBridgeRoutes` 产出的 `InjectedRoute[]` 挂载在固定路径(basePath-relative,
 * `/fetch-bridge`;宿主装配层以 `sse.basePath: "/api"` 补齐 `BRIDGE_PATH = "/api/fetch-bridge"`)
 * ,GET/POST/PUT/PATCH/DELETE/HEAD 各一条,同一 handler。处理顺序严格如下,任一步失败即
 * 短路,不进入下一步(design.md「bridge-routes(修改)」组件块,继承自 aigc-proxy 的
 * `proxy-routes.ts`):
 *
 *   1. `x-pwfb-token` 提取 + 校验(`verifySessionToken`) —— 缺失/malformed/expired/
 *      bad-signature 一律 401,对外文案不区分原因(防探测,原因仅进日志),零上游请求
 *      (Req 2.2)
 *   2. `TARGET_URL_HEADER` 提取 —— 缺失/非绝对 http(s) URL → 400,零上游请求
 *   3. 规则表 `matchRule(rules, host)` —— 未匹配 → 403,零上游请求(Req 2.3)
 *   4. 剔桥接头(`host`/`x-pwfb-token`/`TARGET_URL_HEADER`/逐跳/`content-length`,
 *      **不剔 `authorization`** —— 各 rewrite 自行决定是否动它)得 base headers,构造
 *      `RewriteContext` → `registry.get(rule.rewrite)`:未知 rewrite 名 → 500 清晰错误、
 *      零上游(装配期 `assertRewritesExist` 本应已挡,此处是请求期兜底,Req 1.1, 1.5)
 *   5. 调用 rewrite:返回 `{ error }` → 映射为对应状态码错误响应(如 inject-bearer/
 *      aws-sigv4 缺凭据 → 502),零上游、文案不含密钥(Req 1.3, 3.2, 4.4);否则用返回的
 *      `headers`(必要时 `url`)转发
 *
 * 转发:目标 URL = rewrite 返回的 `url`(如 aws-sigv4/inject-bearer 换址)存在则用之,
 * 否则原始目标 URL 不变;请求 headers 用 rewrite 返回的 `headers`(已含桥接头剔除 +
 * rewrite 自身的凭据处理,如 inject-bearer 注入真实 Bearer、aws-sigv4 重签、passthrough
 * 保留原 `authorization`/`x-amz-*` 不动)。非 GET/HEAD 请求体以 `duplex: "half"` 流式转发
 * (Req 2.5)。
 *
 * 响应:`new Response(upstream.body, { status, headers })` 流式透传(不缓冲),响应
 * headers 剔除逐跳头与 `content-length`。上游 4xx/5xx 状态与体原样透传(Req 2.6)。
 * `fetchImpl` 网络错误 → 502;`AbortSignal.timeout` 触发(仅当 `timeoutMs` 配置,仅约束
 * 上游响应头到达,不约束体流持续)→ 504;错误体固定脱敏文案,绝不包含上游异常细节或
 * 密钥(Req 2.7)。
 *
 * `createFetchBridgeRoutes({ secret, rules, rewrites? })`:`mergeRewrites(rewrites)` 产出
 * 请求期使用的注册表(内置三 rewrite + 同名覆盖的自定义),装配期(工厂调用时,先于任何
 * 请求)`assertRewritesExist(rules, registry)` 校验规则表引用的每个 `rewrite` 名都存在,
 * 拼写错误/漏注册在此 fail-fast,而非运行期才 404/500(Req 1.5)。
 *
 * 日志(`server:fetch-bridge` 命名空间):每请求一行,含 sessionId(token 校验产物;校验
 * 失败前记 "unknown")、host、path、rewrite(规则匹配前记 "unknown")、status、耗时;
 * `authorization` 头与 token 全文绝不落日志(Req 7.1, 7.2)。
 */
import { createLogger } from "@blksails/pi-web-logger";
import { TARGET_URL_HEADER, SESSION_TOKEN_HEADER } from "@blksails/pi-web-fetch-bridge";
import { errorResponse } from "../http/error-map.js";
import type {
  InjectedRoute,
  RequestContext,
  RouteHandler,
} from "../http/handler.types.js";
import { matchRule, assertRewritesExist, type BridgeRule } from "./rules.js";
import { mergeRewrites, type BridgeRewrite, type RewriteContext } from "./rewrites.js";
import { verifySessionToken } from "./session-token.js";

/** 路由模板(固定路径,basePath-relative)。 */
const ROUTE_PATH = "/fetch-bridge";

/** 本代理端点支持的 HTTP 方法(逐个精确注册,Router 按方法精确匹配)。 */
const SUPPORTED_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"] as const;

/**
 * 请求侧固定头(大小写不敏感):构造 {@link RewriteContext} 前统一剔除的桥接头集,
 * 与 rewrite 类型无关。
 * ★ 不含 `authorization`:是否动 `Authorization`(剔除+注入 Bearer、重签、或原样保留)
 * 完全交由具体 rewrite 决定(design.md「bridge-routes(修改)」)——passthrough 的原始
 * SigV4 签名就在 `Authorization` 里,必须逐字节保留;`content-length` 始终剔除(转发体
 * 经流式 `duplex: "half"` 转发,原长度可能因 headers 重组而失真)。
 */
const REQUEST_HEADERS_TO_STRIP_BASE = new Set([
  "host",
  TARGET_URL_HEADER,
  SESSION_TOKEN_HEADER,
  "content-length",
]);

/** 逐跳头(大小写不敏感,`proxy-*` 前缀另行判断)。 */
const HOP_BY_HOP_HEADERS = new Set([
  "connection",
  "keep-alive",
  "transfer-encoding",
  "upgrade",
  "te",
  "trailer",
]);

function isHopByHopOrProxyHeader(lowerName: string): boolean {
  return HOP_BY_HOP_HEADERS.has(lowerName) || lowerName.startsWith("proxy-");
}

/** 对外统一 401 文案(不区分缺失/malformed/expired/bad-signature,防探测)。 */
const UNAUTHORIZED_MESSAGE = "Invalid or expired credentials.";

/**
 * 从专用头 `SESSION_TOKEN_HEADER`(`x-pwfb-token`)提取桥接会话 token;缺失返回 undefined。
 * ★ 不再从 `Authorization` 读:`Authorization` 留给上游自身鉴权(SigV4/token-as-key)。
 * 兼容 `Bearer <token>` 与裸 token 两种写法(client 发裸 token)。
 */
function extractSessionToken(headers: Headers): string | undefined {
  const raw = headers.get(SESSION_TOKEN_HEADER);
  if (raw === null) return undefined;
  const trimmed = raw.trim();
  if (trimmed.length === 0) return undefined;
  const match = /^Bearer\s+(.+)$/i.exec(trimmed);
  return match?.[1] ?? trimmed;
}

/** 解析并校验 `TARGET_URL_HEADER`:必须是绝对 http(s) URL;不合法返回 undefined。 */
function parseTargetUrl(headers: Headers): URL | undefined {
  const raw = headers.get(TARGET_URL_HEADER);
  if (raw === null || raw.length === 0) return undefined;
  let url: URL;
  try {
    url = new URL(raw);
  } catch {
    return undefined;
  }
  if (url.protocol !== "http:" && url.protocol !== "https:") return undefined;
  return url;
}

/** 换址:替换 origin(scheme+host+port),保留路径与查询串。 */
function withUpstreamOrigin(target: URL, upstreamOrigin: string): URL {
  const upstream = new URL(upstreamOrigin);
  const out = new URL(target.toString());
  out.protocol = upstream.protocol;
  out.host = upstream.host;
  return out;
}

/**
 * 规则表 `upstreamOrigin` 字段的统一读取(`inject-bearer`/`aws-sigv4` 可选携带,
 * `passthrough` 判别联合分支不含此字段——透传型不支持换址,design.md 明确)。
 */
function ruleUpstreamOrigin(rule: BridgeRule): string | undefined {
  return "upstreamOrigin" in rule ? rule.upstreamOrigin : undefined;
}

/** 过滤请求 headers:按剔除集合剔除,其余原样透传(逐跳头始终剔除)。 */
function filterRequestHeaders(source: Headers, stripSet: ReadonlySet<string>): Headers {
  const out = new Headers();
  source.forEach((value, name) => {
    const lower = name.toLowerCase();
    if (stripSet.has(lower) || isHopByHopOrProxyHeader(lower)) return;
    out.set(name, value);
  });
  return out;
}

/** 过滤响应 headers:剔除 content-length(流式体长度未知)与逐跳头。 */
function filterResponseHeaders(source: Headers): Headers {
  const out = new Headers();
  source.forEach((value, name) => {
    const lower = name.toLowerCase();
    if (lower === "content-length" || isHopByHopOrProxyHeader(lower)) return;
    out.set(name, value);
  });
  return out;
}

/** `createFetchBridgeRoutes` 的注入依赖。 */
export interface CreateFetchBridgeRoutesDeps {
  /** secret 解析结果(装配期注入,与 token 签发同源;便于测试直接传固定值)。 */
  readonly secret: string | Buffer;
  /** 双型规则表(装配期注入,便于测试直接传固定规则)。 */
  readonly rules: readonly BridgeRule[];
  /** 测试接缝:缺省 `globalThis.fetch`。 */
  readonly fetchImpl?: typeof fetch;
  /** 测试接缝:缺省 `process.env`(真实 key 请求期即时读取,不缓存)。 */
  readonly env?: Record<string, string | undefined>;
  /** 可选转发超时(毫秒);缺省不设额外超时,交上游语义。设置且触发 → 504。 */
  readonly timeoutMs?: number;
  /** 自定义 rewrite 集(同名覆盖内置);与内置三 rewrite 合并为请求期注册表。 */
  readonly rewrites?: ReadonlyMap<string, BridgeRewrite>;
}

/** rewrite 短路错误的 HTTP 状态码 → 错误码(用于统一错误响应体)。 */
function errorCodeForStatus(status: number): string {
  if (status === 502) return "BAD_GATEWAY";
  if (status === 504) return "GATEWAY_TIMEOUT";
  return "INTERNAL";
}

/**
 * 工厂:产出挂载于 `/fetch-bridge`(basePath-relative)的注入路由数组
 * (GET/POST/PUT/PATCH/DELETE/HEAD)。装配期(此调用本身,先于任何请求)校验规则表
 * 引用的每个 `rewrite` 名都存在于合并后的注册表中——拼写错误/漏注册在此 fail-fast
 * (Req 1.5)。
 */
export function createFetchBridgeRoutes(
  deps: CreateFetchBridgeRoutesDeps,
): InjectedRoute[] {
  const { secret, rules, fetchImpl = fetch, env = process.env, timeoutMs } = deps;
  const logger = createLogger({ namespace: "server:fetch-bridge" });
  const registry = mergeRewrites(deps.rewrites);
  assertRewritesExist(rules, registry);

  const handler: RouteHandler = async (ctx: RequestContext): Promise<Response> => {
    const startedAt = Date.now();
    let sessionId = "unknown";
    let rewriteName = "unknown";
    let targetHost = "";

    function logAndReturn(status: number, res: Response): Response {
      logger.info("fetch-bridge request", {
        sessionId,
        host: targetHost,
        path: ctx.url.pathname,
        rewrite: rewriteName,
        status,
        durationMs: Date.now() - startedAt,
      });
      return res;
    }

    // 1) Bearer token 提取 + 校验(缺失/malformed/expired/bad-signature → 401,
    //    对外文案统一,零上游请求,Req 2.2)。
    const token = extractSessionToken(ctx.req.headers);
    if (token === undefined) {
      return logAndReturn(401, errorResponse(401, "UNAUTHORIZED", UNAUTHORIZED_MESSAGE));
    }
    const verified = verifySessionToken({ token, secret });
    if (!verified.ok) {
      return logAndReturn(401, errorResponse(401, "UNAUTHORIZED", UNAUTHORIZED_MESSAGE));
    }
    sessionId = verified.sessionId;

    // 2) TARGET_URL_HEADER 解析(缺失/非绝对 http(s) URL → 400,零上游请求)。
    const targetUrl = parseTargetUrl(ctx.req.headers);
    if (targetUrl === undefined) {
      return logAndReturn(
        400,
        errorResponse(400, "BAD_REQUEST", "Missing or invalid target URL header."),
      );
    }
    targetHost = targetUrl.hostname;

    // 3) 规则表匹配(未匹配 → 403,零上游请求,Req 2.3)。
    const rule = matchRule(rules, targetUrl.hostname);
    if (rule === undefined) {
      return logAndReturn(403, errorResponse(403, "FORBIDDEN", "Target host is not allowed."));
    }
    rewriteName = rule.rewrite;

    // 4) 剔桥接头得 base headers;若规则声明 `upstreamOrigin`(inject-bearer/aws-sigv4
    //    可选携带,passthrough 判别联合分支不含此字段,透传型不支持换址)则先行换址,
    //    换址后的 URL 即为 rewrite 上下文的 `targetUrl`(aws-sigv4 signing 依赖签名 host
    //    与实际发送 host 一致,故须在构造 context 前完成)。
    const upstreamOrigin = ruleUpstreamOrigin(rule);
    const contextTargetUrl =
      upstreamOrigin !== undefined ? withUpstreamOrigin(targetUrl, upstreamOrigin) : targetUrl;
    const baseHeaders = filterRequestHeaders(ctx.req.headers, REQUEST_HEADERS_TO_STRIP_BASE);

    // 5) rewrite 注册表 dispatch(未知 rewrite 名 → 500,零上游;装配期
    //    `assertRewritesExist` 本应已挡,这里是请求期兜底,Req 1.1, 1.5)。
    const rewriteFn = registry.get(rule.rewrite);
    if (rewriteFn === undefined) {
      logger.error("fetch-bridge: unknown rewrite referenced by matched rule", {
        rewrite: rule.rewrite,
        host: targetHost,
      });
      return logAndReturn(
        500,
        errorResponse(500, "INTERNAL", "Server misconfiguration: unknown rewrite."),
      );
    }

    const rewriteContext: RewriteContext = {
      method: ctx.req.method.toUpperCase(),
      targetUrl: contextTargetUrl,
      headers: baseHeaders,
      env,
      rule,
    };
    const result = rewriteFn(rewriteContext);
    if ("error" in result) {
      // rewrite 短路错误(如 inject-bearer/aws-sigv4 缺宿主凭据)→ 映射为对应状态码,
      // 零上游、文案不含密钥(Req 1.3, 3.2, 4.4)。
      return logAndReturn(
        result.error.status,
        errorResponse(
          result.error.status,
          errorCodeForStatus(result.error.status),
          result.error.message,
        ),
      );
    }

    const upstreamUrl = (result.url ?? contextTargetUrl).toString();
    const outHeaders = result.headers;
    const method = rewriteContext.method;

    const init: RequestInit & { duplex?: "half" } = {
      method,
      headers: outHeaders,
    };
    if (method !== "GET" && method !== "HEAD" && ctx.req.body !== null) {
      init.body = ctx.req.body;
      init.duplex = "half";
    }
    if (timeoutMs !== undefined) {
      init.signal = AbortSignal.timeout(timeoutMs);
    }

    let upstream: Response;
    try {
      upstream = await fetchImpl(upstreamUrl, init);
    } catch (err) {
      // AbortSignal.timeout 触发的中止表现为 DOMException("TimeoutError")(WHATWG fetch
      // 标准行为,Node 内置 undici 遵循同规范)→ 504;其余网络层错误 → 502。错误体固定
      // 脱敏文案,绝不透出上游异常细节(Req 2.7)。
      if (err instanceof Error && err.name === "TimeoutError") {
        return logAndReturn(
          504,
          errorResponse(504, "GATEWAY_TIMEOUT", "Upstream request timed out."),
        );
      }
      return logAndReturn(
        502,
        errorResponse(502, "BAD_GATEWAY", "Failed to reach upstream target."),
      );
    }

    // 上游 4xx/5xx 状态与体原样透传(Req 2.6);响应流式转发不缓冲(Req 2.5)。
    const resHeaders = filterResponseHeaders(upstream.headers);
    return logAndReturn(
      upstream.status,
      new Response(upstream.body, { status: upstream.status, headers: resHeaders }),
    );
  };

  return SUPPORTED_METHODS.map((method) => ({
    method,
    path: ROUTE_PATH,
    handler,
  }));
}
