import axios from 'axios';
import type {
  AxiosError,
  AxiosRequestConfig,
  AxiosResponse,
  InternalAxiosRequestConfig,
} from 'axios';
import { showErrorMessage } from '@/utils/feedback';

/** 一个请求的配置。TReq 为请求参数 / 请求体的类型。 */
export type ApiRequestConfig<TReq = unknown> = Omit<
  AxiosRequestConfig<TReq>,
  'signal'
> & {
  url: string;
};

/** 每个调用方可以单独设置的请求选项。 */
export interface RequestOptions<TParse extends boolean = boolean> {
  /** 请求失败时是否自动提示错误信息，默认 true。 */
  showError?: boolean;
  /** 是否合并相同的并发请求，默认 true；为 false 时每次都单独发送。 */
  merge?: boolean;
  /** 是否解析响应：true 返回 response.data，false 返回完整 AxiosResponse。默认 true。 */
  parse?: TParse;
  /** 用于取消当前调用方发起的请求。 */
  signal?: AbortSignal;
}

/** 请求失败时抛出的统一错误，保留 Axios 返回的完整信息。 */
export class ApiError extends Error {
  readonly code?: string;
  readonly status?: number;
  readonly data?: unknown;
  readonly response?: AxiosResponse;
  readonly config?: InternalAxiosRequestConfig;
  readonly isCanceled: boolean;
  readonly originalError?: unknown;
  /** 相同请求被多个调用方共享时，保证错误信息只提示一次。 */
  notified = false;

  constructor(
    message: string,
    options: {
      code?: string;
      status?: number;
      data?: unknown;
      response?: AxiosResponse;
      config?: InternalAxiosRequestConfig;
      isCanceled?: boolean;
      originalError?: unknown;
    } = {},
  ) {
    super(message);
    this.name = 'ApiError';
    this.code = options.code;
    this.status = options.status;
    this.data = options.data;
    this.response = options.response;
    this.config = options.config;
    this.isCanceled = options.isCanceled ?? false;
    this.originalError = options.originalError;
  }
}

const http = axios.create({
  // 开发环境走 Vite 代理（同源），生产环境使用真实接口地址。
  baseURL: import.meta.env.DEV
    ? '/proxy-api'
    : import.meta.env.VITE_API_BASE_URL ?? '',
  timeout: 15000,
});

/** 递归排序对象字段，保证相同参数得到相同的请求 key。 */
const sortValue = (value: unknown): unknown => {
  if (Array.isArray(value)) return value.map(sortValue);
  if (value && typeof value === 'object') {
    return Object.keys(value as Record<string, unknown>)
      .sort()
      .reduce<Record<string, unknown>>((result, key) => {
        result[key] = sortValue((value as Record<string, unknown>)[key]);
        return result;
      }, {});
  }
  return value;
};

const stringify = (value: unknown): string => {
  if (value === undefined) return '';
  try {
    return JSON.stringify(sortValue(value));
  } catch {
    return String(value);
  }
};

const getRequestKey = <TReq>(config: ApiRequestConfig<TReq>): string =>
  [
    (config.method ?? 'GET').toUpperCase(),
    config.url,
    stringify(config.params),
    stringify(config.data),
  ].join('|');

/** 正在进行中的相同请求，值为完整的 AxiosResponse。 */
const pendingRequests = new Map<string, Promise<AxiosResponse>>();

const createCanceledError = () =>
  new ApiError('请求已取消', { isCanceled: true, code: 'ERR_CANCELED' });

const toApiError = (error: unknown): ApiError => {
  if (error instanceof ApiError) return error;

  const axiosError = error as AxiosError<{ message?: string; msg?: string }>;
  const response = axiosError?.response;
  const data = response?.data;
  const serverMessage =
    typeof data?.message === 'string'
      ? data.message
      : typeof data?.msg === 'string'
        ? data.msg
        : undefined;
  const isCanceled = axios.isCancel(error) || axiosError?.code === 'ERR_CANCELED';

  return new ApiError(
    isCanceled ? '请求已取消' : serverMessage ?? axiosError?.message ?? '网络请求失败',
    {
      code: axiosError?.code,
      status: response?.status,
      data,
      response,
      config: axiosError?.config,
      isCanceled,
      originalError: error,
    },
  );
};

/** 发送底层请求并始终保留完整响应；解析与提示交给每个调用方处理。 */
const execute = async <TReq, TRes>(
  config: ApiRequestConfig<TReq>,
  options: RequestOptions,
): Promise<AxiosResponse<TRes>> => {
  try {
    return await http.request<TRes, AxiosResponse<TRes>, TReq>({
      ...config,
      // 合并请求时不能把某个调用方的 signal 交给底层请求，否则会取消其他调用方。
      signal: options.merge === false ? options.signal : undefined,
    });
  } catch (error) {
    throw toApiError(error);
  }
};

/** 后端统一响应包装：{ code, message, data, success }。 */
interface ResultEnvelope {
  code: number | string;
  message?: string;
  data?: unknown;
  success?: boolean;
}

const isResultEnvelope = (body: unknown): body is ResultEnvelope => {
  if (!body || typeof body !== 'object') return false;
  const record = body as Record<string, unknown>;
  return (
    'code' in record &&
    ('data' in record || 'success' in record || 'message' in record)
  );
};

const isSuccessCode = (code: number | string) => code === 200;

const formatResponse = <TRes, TParse extends boolean>(
  response: AxiosResponse<TRes>,
  options: RequestOptions<TParse>,
): TParse extends true ? TRes : AxiosResponse<TRes> => {
  if (options.parse === false) {
    return response as TParse extends true ? TRes : AxiosResponse<TRes>;
  }

  const body = response.data as unknown;
  if (isResultEnvelope(body)) {
    const succeeded = isSuccessCode(body.code) || body.success === true ;
    if (!succeeded) {
      throw new ApiError(
        body.message || '请求失败',
        {
          code: String(body.code),
          status: response.status,
          data: body,
          response,
          config: response.config,
        },
      );
    }
    return body.data as TParse extends true ? TRes : AxiosResponse<TRes>;
  }

  return body as TParse extends true ? TRes : AxiosResponse<TRes>;
};

const notifyError = (error: ApiError, options: RequestOptions) => {
  if (error.isCanceled || options.showError === false || error.notified) return;
  error.notified = true;
  showErrorMessage(error.message);
};

/** 让共享请求对每个调用方单独支持取消。 */
const raceWithSignal = <T>(promise: Promise<T>, signal: AbortSignal): Promise<T> => {
  if (signal.aborted) return Promise.reject(createCanceledError());

  return new Promise<T>((resolve, reject) => {
    const onAbort = () => reject(createCanceledError());
    signal.addEventListener('abort', onAbort, { once: true });

    promise.then(
      (value) => {
        signal.removeEventListener('abort', onAbort);
        resolve(value);
      },
      (error) => {
        signal.removeEventListener('abort', onAbort);
        reject(error);
      },
    );
  });
};

/**
 * 发送网络请求。
 *
 * 默认合并相同的并发请求、自动提示错误，并且只返回服务端的 data。
 *
 * @example
 * const result = await request<{ keyword: string }, PageResult<User>>({
 *   url: '/api/users',
 *   method: 'GET',
 *   params: { keyword },
 * });
 *
 * // 不解析，拿到完整的 AxiosResponse
 * const response = await request<void, User[]>(
 *   { url: '/api/users' },
 *   { parse: false },
 * );
 *
 * // 不合并、不自动提示，并通过 signal 取消
 * const controller = new AbortController();
 * await request({ url: '/api/users' }, {
 *   merge: false,
 *   showError: false,
 *   signal: controller.signal,
 * });
 */
export const request = async <
  TReq = unknown,
  TRes = unknown,
  TParse extends boolean = true,
>(
  config: ApiRequestConfig<TReq>,
  options: RequestOptions<TParse> = {},
): Promise<TParse extends true ? TRes : AxiosResponse<TRes>> => {
  const { signal, merge = true } = options;

  if (signal?.aborted) {
    throw createCanceledError();
  }

  let task: Promise<AxiosResponse<TRes>>;

  if (merge) {
    const key = getRequestKey(config);
    const existing = pendingRequests.get(key) as Promise<AxiosResponse<TRes>> | undefined;

    if (existing) {
      task = existing;
    } else {
      task = execute<TReq, TRes>(config, options);
      pendingRequests.set(key, task);

      const cleanup = () => {
        if (pendingRequests.get(key) === task) {
          pendingRequests.delete(key);
        }
      };
      task.then(cleanup, cleanup);
    }
  } else {
    task = execute<TReq, TRes>(config, options);
  }

  try {
    const response = merge && signal ? await raceWithSignal(task, signal) : await task;
    return formatResponse(response, options);
  } catch (error) {
    const apiError = toApiError(error);
    notifyError(apiError, options);
    throw apiError;
  }
};
