import { API_BASE_URL } from '../constants/env';

export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';

export class HttpError extends Error {
  readonly name = 'HttpError';
  readonly url: string;
  readonly status: number;
  readonly statusText: string;
  readonly responseText?: string;

  constructor(args: { message: string; url: string; status: number; statusText: string; responseText?: string }) {
    super(args.message);
    this.url = args.url;
    this.status = args.status;
    this.statusText = args.statusText;
    this.responseText = args.responseText;
  }
}

export type HttpJsonOptions = {
  /**
   * 支持传入完整 URL 或以 / 开头的 path（会自动拼到 API_BASE_URL）
   */
  path: string;
  method?: HttpMethod;
  headers?: Record<string, string>;
  /**
   * 将对象自动 JSON.stringify；GET 请求请不要传 body
   */
  body?: unknown;
  /**
   * 超时（毫秒），默认 10 秒
   */
  timeoutMs?: number;
  /**
   * 外部 signal（例如上层取消请求）
   */
  signal?: AbortSignal;
  /**
   * 仅开发环境日志：便于联调排查
   */
  debugLabel?: string;
};

function isAbsoluteUrl(path: string): boolean {
  return /^https?:\/\//i.test(path);
}

function joinUrl(baseUrl: string, path: string): string {
  const base = (baseUrl || '').replace(/\/+$/, '');
  const p = (path || '').trim();
  if (!p) return base;
  if (p.startsWith('/')) return `${base}${p}`;
  return `${base}/${p}`;
}

function createTimeoutAbortSignal(timeoutMs: number, external?: AbortSignal): AbortController {
  const controller = new AbortController();

  const t = setTimeout(() => controller.abort(), timeoutMs);

  // 避免 Node/Vitest 下悬挂定时器影响退出
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  (t as any)?.unref?.();

  if (external) {
    if (external.aborted) {
      controller.abort();
    } else {
      external.addEventListener(
        'abort',
        () => {
          controller.abort();
        },
        { once: true },
      );
    }
  }

  return controller;
}

function isDev(): boolean {
  return typeof __DEV__ !== 'undefined' && __DEV__;
}

export async function httpJson<T>(opts: HttpJsonOptions): Promise<T> {
  const method = opts.method ?? 'GET';
  const timeoutMs = opts.timeoutMs ?? 10_000;
  const url = isAbsoluteUrl(opts.path) ? opts.path : joinUrl(API_BASE_URL, opts.path);

  const headers: Record<string, string> = {
    ...(opts.headers ?? {}),
  };

  const hasBody = typeof opts.body !== 'undefined' && opts.body !== null;
  const body = hasBody ? JSON.stringify(opts.body) : undefined;

  // 仅在有 body 时默认补齐 Content-Type，避免 GET 请求无意义携带
  if (hasBody && !headers['Content-Type']) {
    headers['Content-Type'] = 'application/json';
  }

  if (isDev() && opts.debugLabel) {
    console.log(`[${opts.debugLabel}] 请求地址:`, url);
    console.log(`[${opts.debugLabel}] 请求方法:`, method);
    console.log(`[${opts.debugLabel}] 请求头:`, headers);
    if (hasBody) console.log(`[${opts.debugLabel}] 请求体:`, opts.body);
  }

  const controller = createTimeoutAbortSignal(timeoutMs, opts.signal);

  let res: Response;
  try {
    res = await fetch(url, {
      method,
      headers,
      body,
      signal: controller.signal,
    });
  } catch (e) {
    // RN 下 AbortError 文案不完全一致，这里统一对外语义
    const msg = e instanceof Error ? e.message : String(e);
    // 带上 URL，便于在 TestFlight/Release 排查实际打到哪个地址（例如误打到 localhost）
    throw new Error(`网络请求失败：${msg}（${url}）`);
  }

  if (!res.ok) {
    const text = await res.text().catch(() => '');
    throw new HttpError({
      message: `HTTP 请求失败：${res.status} ${res.statusText} ${text}`.trim(),
      url,
      status: res.status,
      statusText: res.statusText,
      responseText: text,
    });
  }

  // 204/205 无内容时不要强行 parse
  if (res.status === 204 || res.status === 205) {
    return undefined as unknown as T;
  }

  // 某些后端/网关会返回 200 但 body 为空；此时 res.json() 会抛错，导致客户端误判“失败”。
  // 这里改为：先读 text，空则返回 undefined；非空再 parse JSON。
  const text = await res.text().catch(() => '');
  if (!text || !String(text).trim()) {
    return undefined as unknown as T;
  }
  try {
    return JSON.parse(text) as T;
  } catch {
    throw new HttpError({
      message: `HTTP 响应不是合法 JSON：${res.status} ${res.statusText} ${text}`.trim(),
      url,
      status: res.status,
      statusText: res.statusText,
      responseText: text,
    });
  }
}

