/**
 * 统一的 Edge Function 调用客户端
 *
 * 在 supabase.functions.invoke 之上提供：
 * - 超时控制（默认 30s，可覆盖）
 * - 指数退避重试（默认 0 次，仅对网络/5xx 错误重试）
 * - 自动性能上报（接入 perfReporter，metric_type=api_call）
 * - 统一错误归一（抛出带 code/status/traceId 的 EdgeError）
 * - 可选 trace_id 透传（后端 _shared/trace.ts 配套）
 *
 * 用法：
 *   import { invokeEdge } from "@/lib/edgeClient";
 *   const data = await invokeEdge<MyResp>("my-fn", { body: { ... } });
 */
import { supabase } from "@/integrations/supabase/client";
import { reportMetric } from "@/lib/perfReporter";
import { reportError } from "@/lib/errorReporter";

export interface InvokeOptions {
  body?: unknown;
  headers?: Record<string, string>;
  /** 总超时（毫秒），默认 30000 */
  timeoutMs?: number;
  /** 失败重试次数（不含首次），默认 0；仅对 network / 5xx 重试 */
  retries?: number;
  /** 指数退避基数（毫秒），默认 300 */
  retryBaseMs?: number;
  /** 显式 traceId（不传则由调用链生成） */
  traceId?: string;
  /** 是否上报性能（默认 true） */
  reportPerf?: boolean;
}

export class EdgeError extends Error {
  status?: number;
  code?: string;
  traceId?: string;
  details?: unknown;
  constructor(
    message: string,
    opts: { status?: number; code?: string; traceId?: string; details?: unknown } = {},
  ) {
    super(message);
    this.name = "EdgeError";
    this.status = opts.status;
    this.code = opts.code;
    this.traceId = opts.traceId;
    this.details = opts.details;
  }
}

function genTraceId(): string {
  if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
    return crypto.randomUUID();
  }
  return `t_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
}

function isRetriable(err: unknown): boolean {
  if (err instanceof EdgeError) {
    if (!err.status) return true; // network
    return err.status >= 500 && err.status < 600;
  }
  // AbortError / fetch network error
  return true;
}

async function invokeOnce<T>(
  fnName: string,
  body: unknown,
  headers: Record<string, string>,
  timeoutMs: number,
): Promise<T> {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    // supabase-js v2 invoke 不直接支持 AbortSignal，用 Promise.race 兜底
    const invokePromise = supabase.functions.invoke(fnName, {
      body: body as any,
      headers,
    });
    const abortPromise = new Promise<never>((_, reject) => {
      controller.signal.addEventListener("abort", () => {
        reject(new EdgeError(`Edge function "${fnName}" timeout (${timeoutMs}ms)`, {
          code: "TIMEOUT",
        }));
      });
    });
    const { data, error } = (await Promise.race([invokePromise, abortPromise])) as {
      data: T | null;
      error: { message?: string; status?: number; context?: any } | null;
    };
    if (error) {
      throw new EdgeError(error.message || "Edge function error", {
        status: (error as any).status,
        code: (error as any).code,
        details: error,
      });
    }
    return data as T;
  } finally {
    clearTimeout(timer);
  }
}

export async function invokeEdge<T = unknown>(
  fnName: string,
  options: InvokeOptions = {},
): Promise<T> {
  const {
    body,
    headers: extraHeaders,
    timeoutMs = 30_000,
    retries = 0,
    retryBaseMs = 300,
    traceId = genTraceId(),
    reportPerf = true,
  } = options;

  const headers: Record<string, string> = {
    "x-trace-id": traceId,
    ...(extraHeaders ?? {}),
  };

  const start = performance.now();
  let attempt = 0;
  let lastErr: unknown;

  while (attempt <= retries) {
    try {
      const result = await invokeOnce<T>(fnName, body, headers, timeoutMs);
      if (reportPerf) {
        reportMetric({
          metric_type: "api_call",
          metric_name: `edge:${fnName}`,
          duration_ms: Math.round(performance.now() - start),
          context: { ok: true, attempts: attempt + 1, traceId },
        });
      }
      return result;
    } catch (err) {
      lastErr = err;
      const canRetry = attempt < retries && isRetriable(err);
      if (!canRetry) break;
      const backoff = retryBaseMs * Math.pow(2, attempt);
      await new Promise((r) => setTimeout(r, backoff));
      attempt += 1;
    }
  }

  if (reportPerf) {
    reportMetric({
      metric_type: "api_call",
      metric_name: `edge:${fnName}`,
      duration_ms: Math.round(performance.now() - start),
      context: {
        ok: false,
        attempts: attempt + 1,
        traceId,
        error: lastErr instanceof Error ? lastErr.message : String(lastErr),
      },
    });
  }

  // 上报错误日志（仅最终失败）
  void reportError({
    message:
      lastErr instanceof Error
        ? `[edge:${fnName}] ${lastErr.message}`
        : `[edge:${fnName}] unknown error`,
    stack: lastErr instanceof Error ? lastErr.stack : undefined,
    severity: "error",
    context: {
      source: "edgeClient",
      fnName,
      traceId,
      attempts: attempt + 1,
      status: lastErr instanceof EdgeError ? lastErr.status : undefined,
      code: lastErr instanceof EdgeError ? lastErr.code : undefined,
    },
  });

  throw lastErr instanceof Error
    ? lastErr
    : new EdgeError(String(lastErr), { traceId });
}
