/**
 * HTTP 客户端封装（axios）。
 *
 * - `baseURL`：开发环境通常为空，走 Vite 同源代理；生产可配 `VITE_API_BASE_URL`。
 * - 请求拦截：除白名单接口外，自动附加 `Authorization: Bearer <accessToken>`。
 * - 错误：`postJson` 将 axios 错误统一转为 `Error`，便于页面层 `try/catch`。
 */
import axios, { AxiosError, type InternalAxiosRequestConfig } from "axios";
import {
  REFRESH_TOKEN_KEY,
  SessionExpiredError,
  invalidateSessionAndGoLogin,
  persistTokens,
  refreshTokens,
} from "./auth";
import { ApiPath, pathsWithoutBearerAuth } from "./paths";

const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "";

type JsonBody = Record<string, unknown>;

export const httpClient = axios.create({
  baseURL: API_BASE_URL,
  timeout: 30000,
});

httpClient.interceptors.request.use((config) => {
  const path = config.url ?? "";
  const skipBearer = pathsWithoutBearerAuth.some((p) => path === p || path.endsWith(p));
  if (skipBearer) {
    config.headers.delete("Authorization");
    return config;
  }
  const accessToken = window.localStorage.getItem("accessToken");
  if (accessToken) {
    config.headers.set("Authorization", `Bearer ${accessToken}`);
  }
  return config;
});

type SessionRetryConfig = InternalAxiosRequestConfig & { _sessionRetried?: boolean };

/** 同一时刻共用一个 refresh，避免并发 401 打爆续签接口 */
let refreshOnce: Promise<void> | null = null;

function isAuthRefreshUrl(url: string): boolean {
  return url === ApiPath.authRefresh || url.endsWith(ApiPath.authRefresh);
}

httpClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (!axios.isAxiosError(error) || !error.config) {
      return Promise.reject(error);
    }
    const status = error.response?.status;
    const config = error.config as SessionRetryConfig;
    if (status !== 401) {
      return Promise.reject(error);
    }

    const path = config.url ?? "";
    if (isAuthRefreshUrl(path)) {
      invalidateSessionAndGoLogin();
      return Promise.reject(new SessionExpiredError());
    }
    if (pathsWithoutBearerAuth.some((p) => path === p || path.endsWith(p))) {
      return Promise.reject(error);
    }
    if (config._sessionRetried) {
      invalidateSessionAndGoLogin();
      return Promise.reject(new SessionExpiredError());
    }
    config._sessionRetried = true;

    if (!refreshOnce) {
      refreshOnce = (async () => {
        const rt = window.localStorage.getItem(REFRESH_TOKEN_KEY);
        if (!rt) {
          invalidateSessionAndGoLogin();
          throw new SessionExpiredError();
        }
        try {
          const data = await refreshTokens(rt);
          persistTokens(data.accessToken, data.refreshToken);
        } catch {
          invalidateSessionAndGoLogin();
          throw new SessionExpiredError();
        }
      })().finally(() => {
        refreshOnce = null;
      });
    }

    try {
      await refreshOnce;
    } catch (e) {
      return Promise.reject(e);
    }
    return httpClient.request(config);
  },
);

function toRequestError(error: unknown, fallback: string): Error {
  if (axios.isAxiosError(error)) {
    const axiosError = error as AxiosError<unknown>;
    const responseData = axiosError.response?.data;
    if (typeof responseData === "string" && responseData) {
      return new Error(responseData);
    }
    if (responseData && typeof responseData === "object" && "message" in responseData) {
      const message = (responseData as Record<string, unknown>).message;
      if (typeof message === "string" && message) return new Error(message);
    }
    return new Error(axiosError.message || fallback);
  }
  return error instanceof Error ? error : new Error(fallback);
}

/**
 * GET JSON，支持 query（值为 `undefined` 的键不拼接）。
 */
export async function getJson<TResponse>(
  path: string,
  query?: Record<string, string | number | undefined>,
  signal?: AbortSignal,
): Promise<TResponse> {
  const sp = new URLSearchParams();
  if (query) {
    for (const [k, v] of Object.entries(query)) {
      if (v === undefined) continue;
      sp.set(k, String(v));
    }
  }
  const qs = sp.toString();
  const url = qs ? `${path}?${qs}` : path;
  try {
    const response = await httpClient.get<TResponse>(url, { signal });
    return response.data;
  } catch (error) {
    throw toRequestError(error, "Request failed");
  }
}

/**
 * POST JSON，返回解析后的响应体 `data`。
 * @param path 以 `/` 开头的相对路径
 */
export async function postJson<TResponse>(
  path: string,
  body: JsonBody,
  signal?: AbortSignal,
): Promise<TResponse> {
  try {
    const response = await httpClient.post<TResponse>(path, body, { signal });
    return response.data;
  } catch (error) {
    throw toRequestError(error, "Request failed");
  }
}

/** PATCH JSON，返回解析后的响应体 `data`。 */
export async function patchJson<TResponse>(
  path: string,
  body: JsonBody,
  signal?: AbortSignal,
): Promise<TResponse> {
  try {
    const response = await httpClient.patch<TResponse>(path, body, { signal });
    return response.data;
  } catch (error) {
    throw toRequestError(error, "Request failed");
  }
}

/** DELETE JSON，返回解析后的响应体 `data`（若接口无返回体可使用 `void` 类型接收）。 */
export async function deleteJson<TResponse>(
  path: string,
  signal?: AbortSignal,
): Promise<TResponse> {
  try {
    const response = await httpClient.delete<TResponse>(path, { signal });
    return response.data;
  } catch (error) {
    throw toRequestError(error, "Request failed");
  }
}

/**
 * POST JSON，返回原生 `Response`（用于需要直接读 body stream 的场景）。
 * 注意：不走 axios 拦截器以外的逻辑；若需鉴权请自行在 headers 中传入。
 */
export async function postStream(
  path: string,
  body: JsonBody,
  signal?: AbortSignal,
): Promise<Response> {
  const response = await fetch(`${API_BASE_URL}${path}`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
    signal,
  });

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(errorText || `Stream request failed with status ${response.status}`);
  }

  return response;
}
