import {
  NoopObject,
  addTrailingSlash,
  queryString,
  removeHeadingSlash,
} from "@dage/utils";
import { createHeader } from "./Header";
import { stringReplace, variableReplace } from "./replace";
import {
  DageRequest,
  DageRequestConfig,
  DageResponse,
  Interceptor,
  PaginationParams,
  PaginationResponse,
} from "./types";
import {
  detectContentType,
  headersToObject,
  methodsHasBody,
  serializeBody,
  serializeResponseBody,
} from "./utils";
import { ResponseError } from "./error";

export interface ServiceAdapter {
  baseURL: string;
  fetch: typeof fetch;
  /**
   * 拦截器
   */
  interceptor?: Interceptor;
  /**
   * 全局变量
   */
  globalVariables?: Record<string, any>;
}

const DEFAULT_INTERCEPTOR: Interceptor = (_, next) => {
  return next();
};

export function createService() {
  let adapter: ServiceAdapter;
  let initialized = false;

  /**
   * 是否初始化完成
   * @returns
   */
  function isInitialized() {
    return initialized;
  }

  /**
   * 获取 Base URL
   */
  function getBaseURL() {
    if (!initialized) {
      throw new Error("未初始化");
    }

    return adapter.baseURL;
  }

  /**
   * 解析响应
   */
  function parseResponse(
    response: DageResponse | PaginationResponse
  ): DageResponse | PaginationResponse {
    if (response == null) {
      throw {
        message: "未能识别响应",
        code: -1,
      };
    }

    if (response.code !== 0) {
      throw new ResponseError({
        ...response,
        errorMessage: response.errorMessage ?? "请求失败",
        errorCode: response.errorCode ?? -1,
      });
    }

    return response;
  }

  /**
   * 初始化
   * @param options
   */
  function initial(options: ServiceAdapter) {
    if (initialized && process.env.NODE_ENV === "development") {
      console.error("[@dage/service] 已经初始化过了, 不需要重复初始化");
    }

    adapter = options;

    adapter.baseURL = addTrailingSlash(options.baseURL);

    initialized = true;
  }

  async function normalizeRequest<R = any, P extends {} = any>(
    url: string,
    body?: P,
    config: DageRequestConfig = NoopObject
  ): Promise<PaginationResponse<R> | DageResponse<R>> {
    if (!initialized) {
      throw new Error("请先调用 initial 初始化");
    }

    const method = config.method ?? "POST";
    const searchParams: Record<string, string> = {};
    const reqBody = body ?? NoopObject;

    // 路由参数替换
    url = stringReplace(url, reqBody);

    // 查询字符串处理
    const appendSearchParams = (
      obj: Record<string, string>,
      params = searchParams
    ) => {
      Object.assign(params, obj);
    };

    if (config.searchParams) {
      appendSearchParams(config.searchParams);
    }

    // 报头处理
    const headers: Record<string, string> = createHeader();

    if (config.headers) {
      Object.keys(config.headers).forEach((key) => {
        headers[key] = config.headers![key];
      });
    }

    if (methodsHasBody(method) && !headers["Content-Type"]) {
      const contentType = detectContentType(reqBody);
      if (contentType) {
        headers["Content-type"] = contentType;
      }
    }

    const requestPayload: DageRequest = {
      name: url,
      method,
      body: reqBody,
      searchParams,
      headers,
      meta: config.meta ?? NoopObject,
    };

    // 变量替换
    if (adapter.globalVariables) {
      variableReplace(requestPayload.body, adapter.globalVariables);
      variableReplace(requestPayload.searchParams, adapter.globalVariables);
      variableReplace(requestPayload.headers, adapter.globalVariables);
    }

    const interceptor = adapter.interceptor ?? DEFAULT_INTERCEPTOR;
    // next 方法被拦截器调用的次数，如果调用次数过多，说明程序存在bug，不排除有无限循环的可能
    let nextCallTime = 0;

    const response = await interceptor(requestPayload, async () => {
      nextCallTime++;

      // 最多 3 次
      if (nextCallTime > 3) {
        throw new Error(
          "拦截器调用 next 次数过多, 请检查代码，可能存在无限循环"
        );
      }

      if (requestPayload.body && requestPayload.method === "GET") {
        appendSearchParams(requestPayload.body, requestPayload.searchParams);
      }

      // 构建 href
      let href = url.startsWith("http")
        ? url
        : adapter.baseURL + removeHeadingSlash(url);

      if (Object.keys(requestPayload.searchParams).length) {
        const temp = queryString.parseUrl(href);
        href = queryString.stringifyUrl({
          url: temp.url,
          query: { ...temp.query, ...requestPayload.searchParams },
          fragmentIdentifier: temp.fragmentIdentifier,
        });
      }

      /**
       * 主体处理
       */
      const finalBody =
        requestPayload.body && methodsHasBody(requestPayload.method)
          ? serializeBody(requestPayload.headers, requestPayload.body)
          : undefined;

      const res = await adapter.fetch(href, {
        method: requestPayload.method,
        headers: requestPayload.headers,
        body: finalBody,
        mode: "cors",
      });

      if (!res.ok) {
        console.error(res);
        throw new Error(`[@dage/service]请求 ${url} 失败: ${res.status}`);
      }

      const resData = await serializeResponseBody(res, config);

      // @ts-expect-error
      const clone: DageResponse = {
        ...resData,
        // 原始请求信息
        __raw__: {
          statusCode: res.status,
          data: resData,
          header: headersToObject(res.headers),
        },
      };

      return clone as DageResponse | PaginationResponse;
    });

    // 解析协议
    return parseResponse(response);
  }

  /**
   * 请求方法，默认使用 POST 方法
   */
  async function request<R = any, P extends {} = any>(
    url: string,
    body?: P,
    config?: DageRequestConfig
  ): Promise<R> {
    return (await normalizeRequest(url, body, config)).data;
  }

  const requestByPost = request;

  async function requestByGet<R = any, P extends {} = any>(
    url: string,
    body?: P,
    config?: DageRequestConfig
  ): Promise<R> {
    return (await normalizeRequest(url, body, { method: "GET", ...config }))
      .data;
  }

  /**
   * 列表接口请求, 默认为 POST 请求
   */
  async function requestPagination<R = any, P extends PaginationParams = any>(
    name: string,
    body?: P,
    config?: DageRequestConfig
  ): Promise<PaginationResponse<R>> {
    return (await normalizeRequest(name, body, {
      method: "POST",
      ...config,
    })) as PaginationResponse<R>;
  }

  return {
    getBaseURL,
    parseResponse,
    initial,
    isInitialized,
    request,
    requestByPost,
    requestByGet,
    requestPagination,
  };
}

const DEFAULT_SERVICE = createService();

/**
 * 获取 Base URL
 * @returns
 */
export const getBaseURL = DEFAULT_SERVICE.getBaseURL;

/**
 * 解析响应
 * @param response
 * @returns
 */
export const parseResponse = DEFAULT_SERVICE.parseResponse;

/**
 * 是否初始化完成
 * @returns
 */
export const isInitialized = DEFAULT_SERVICE.isInitialized;

/**
 * 初始化
 * @param options
 */
export const initial = DEFAULT_SERVICE.initial;

export const request = DEFAULT_SERVICE.request;

export const requestByPost = DEFAULT_SERVICE.requestByPost;

export const requestByGet = DEFAULT_SERVICE.requestByGet;

export const requestPagination = DEFAULT_SERVICE.requestPagination;
