import type { Result } from '@/types';
import axios, { isAxiosError, type AxiosError, type AxiosRequestConfig, type AxiosResponse, type CreateAxiosDefaults, type InternalAxiosRequestConfig } from 'axios';

export type HttpResponse<T> = [err: AxiosError | null, data: T | null, resp: Result<T> | null];

export interface HttpRequestOptions extends CreateAxiosDefaults {
  requestInterceptor?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>;
  responseInterceptor?: (resp: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>;
  responseErrorInterceptor?: (error: any) => any;
}

export const createHttpClient = (config?: HttpRequestOptions) => {
  const defaultRequestInterceptor = (config: InternalAxiosRequestConfig) => config;
  const defaultResponseInterceptor = (response: AxiosResponse) => response;
  const defaultResponseErrorInterceptor = (error: any) => {
    if (isAxiosError(error)) {
      if (error.status === 401) {
        // 处理 401 错误
      }
      if (error.status === 404) {
        // 处理 404 错误
      }
    }
    return Promise.reject(error);
  };

  const requestInterceptor = config?.requestInterceptor ?? defaultRequestInterceptor;
  const responseInterceptor = config?.responseInterceptor ?? defaultResponseInterceptor;
  const responseErrorInterceptor = config?.responseErrorInterceptor ?? defaultResponseErrorInterceptor;

  const instance = axios.create(config);
  instance.interceptors.request.use(requestInterceptor);
  instance.interceptors.response.use(responseInterceptor, responseErrorInterceptor);

  const httpGet = <T>(url: string, options?: AxiosRequestConfig & { retRaw?: boolean }): Promise<HttpResponse<T>> => {
    const { retRaw, ...reqConfig } = options ?? {};
    return new Promise((resolve) => {
      instance
        .get(url, {
          ...reqConfig,
        })
        .then((res) => {
          if (retRaw) {
            resolve([null, res.data as T, null]);
          } else {
            const resData = res.data as Result<T>;
            resolve([null, resData.data, resData]);
          }
        })
        .catch((err) => {
          resolve([err as AxiosError, null, null]);
        });
    });
  };

  const httpPost = <T>(url: string, data?: AxiosRequestConfig['data'], options?: Partial<Omit<AxiosRequestConfig, 'data'>> & { retRaw?: boolean; upload?: boolean }): Promise<HttpResponse<T>> => {
    const { retRaw, upload, ...reqConfig } = options ?? {};
    return new Promise((resolve) => {
      instance
        .post(url, data, { headers: { 'content-type': upload ? 'multipart/form-data' : 'application/json' }, ...reqConfig })
        .then((res) => {
          const resData = res.data;
          if (retRaw) {
            resolve([null, resData as T, null]);
          } else {
            resolve([null, resData.data as T, resData as Result<T>]);
          }
        })
        .catch((err) => {
          resolve([err as AxiosError, null, null]);
        });
    });
  };

  const httpPut = <T>(url: string, data?: AxiosRequestConfig['data'], options?: Partial<Omit<AxiosRequestConfig, 'data'>>): Promise<HttpResponse<T>> => {
    const reqConfig = options ?? {};
    return new Promise((resolve) => {
      instance
        .put<Result<T>>(url, data, { ...reqConfig })
        .then((res) => {
          resolve([null, res.data.data, res.data]);
        })
        .catch((err) => {
          resolve([err as AxiosError, null, null]);
        });
    });
  };

  const httpDelete = <T>(url: string, idList: string[], options?: Partial<Omit<AxiosRequestConfig, 'data'>>): Promise<HttpResponse<T>> => {
    const reqConfig = options ?? {};
    return new Promise((resolve) => {
      instance
        .delete<Result<T>>(url, { ...reqConfig, data: idList })
        .then((res) => {
          resolve([null, res.data.data, res.data]);
        })
        .catch((err) => {
          resolve([err as AxiosError, null, null]);
        });
    });
  };

  return {
    get clientInstance() {
      return instance;
    },
    get: httpGet,
    post: httpPost,
    put: httpPut,
    delete: httpDelete,
  };
};

// 从响应中解析出数据
export const unwrapResponse = <T>(resp: HttpResponse<T>) => {
  const [err, data, result] = resp;

  // 如果 err 存在说明有 http 错误，那么直接抛出错误，
  // 如果没有 err，那么接下来判断是否存在业务错误。

  // 如果 result 中 isSuccess 为 false，说明有业务错误，
  // 如果 result 不存在，说明 retRaw 为 true，是直接返回 data 的情况。

  if (err) throw err;

  // 不能判断 !result 从而提前结束分支，
  // 因为当 retRaw 为 true 时，result 总是为 null。
  if (result) {
    const { isSuccess, path, msg, errorMsg } = result;
    if (!isSuccess) throw new Error(`${path ? `${path}: ` : ''}${msg || errorMsg || '请求失败'}`);
  }

  // 不做严格判空，null == undefined -> true
  if (data == null) throw new Error('响应数据为空');

  return data;
};

export const unwrapNullableResponse = <T>(resp: HttpResponse<T>) => {
  const [err, data, result] = resp;
  if (err) throw err;
  if (result) {
    const { isSuccess, path, msg, errorMsg } = result;
    if (!isSuccess) throw new Error(`${path ? `${path}: ` : ''}${msg || errorMsg || '请求失败'}`);
  }
  if (data === undefined) throw new Error('响应数据不存在');
  return data;
};

// 针对没有数据的响应，直接判断是否存在错误
export const unwrapVoidResponse = (resp: HttpResponse<void>) => {
  const [err, , result] = resp;
  if (err) throw err;
  if (result) {
    const { isSuccess, path, msg, errorMsg } = result;
    if (!isSuccess) throw new Error(`${path ? `${path}: ` : ''}${msg || errorMsg || '请求失败'}`);
  }
};

// 从错误中解析出错误信息
export const parseErrorFeedback = (error: Error) => {
  // 当发生 http 错误时，unwrapResponse 会直接抛出错误，
  // 所以如果不是 AxiosError，说明是业务错误，直接返回错误信息。
  if (!isAxiosError(error)) {
    return error.message;
  }

  // 如果是 AxiosError，说明是 http 错误，尝试获取 result，
  const result = error.response?.data;

  // 如果 result 不存在，返回 error 中原生的 code + message
  if (!result) {
    const { code, message } = error;
    return `${code ? `code: ${code}, ` : ''}${message ? `message: ${message}` : ''}`;
  }

  // 如果 result 存在，判断是否是字符串，
  // 如果是字符串，说明是一些奇怪的、直接返回的错误信息，直接返回；
  // 如果不是字符串，说明是 Result 类型，返回其中的 path + msg + errorMsg。
  if (typeof result === 'string') {
    return result;
  }
  const { path, msg, errorMsg } = result as Result;
  return `${path ? `${path}: ` : ''}${msg || errorMsg || '请求失败'}`;
};
