import RequestApiConfig from './RequestApiConfig';
import { DataModel, type NewDataModel } from '@imengyu/js-request-transform';
import { stringHashCode } from '../utils/Utils';
import { RequestApiError, RequestApiResult } from './RequestApiResult';
import { defaultResponseDataHandler, defaultResponseErrorHandler } from './RequestHandler';
import type { HeaderType, QueryParams, TypeSaveable } from '../utils/AllType';
import type { KeyValue } from '@imengyu/js-request-transform/dist/DataUtils';
import type { RequestImplementer } from './RequestImplementer';
import { FormData, type Response } from '../implementer/Uniapp';

/**
 * API 请求核心
 *
 * 功能介绍：
 *    本类是对 fetch 的封装，提供了基本的请求功能。
 *
 * Author: imengyu
 * Date: 2022/03/28
 *
 * Copyright (c) 2021 imengyu.top. Licensed under the MIT License.
 * See License.txt in the project root for license information.
 */

/**
 * 请求配置体
 */
export interface RequestCoreConfig<T extends DataModel> {
  /**
   * 基础URL
   */
  baseUrl: string;
  /**
   * 错误代码字符串数据
   */
  errCodes: { [index: number]: string };
  /**
   * 默认携带header
   */
  defaultHeader: HeaderType,
  /**
   * 超时时间 ms
   */
  timeout: number,
  /**
   * 请求拦截
   */
  requestInceptor?: (url: string, req: RequestOptions) => { newUrl: string, newReq: RequestOptions };
  /**
   * 响应拦截
   */
  responseInceptor?: (response: Response) => Response;
  /**
   * 错误报告拦截。如果返回true，则不进行错误报告
   */
  responseErrReoprtInceptor?: (instance: RequestCoreInstance<T>, err: RequestApiError) => boolean;
  /**
   * 错误报告函数
   */
  reportError?: (instance: RequestCoreInstance<T>, err: RequestApiError|Error) => void;

  /**
   * 自定义数据处理函数
   */
  responseDataHandler?: (response: Response, req: RequestOptions, resultModelClass: NewDataModel|undefined, instance: RequestCoreInstance<T>, apiName: string|undefined) => Promise<RequestApiResult<T>>;
  /**
   * 自定义错误处理函数
   */
  responseErrorHandler?: (err: Error, instance: RequestCoreInstance<T>, apiName: string|undefined) => RequestApiError;
  /**
   * 类自定义创建函数
   */
  modelClassCreator: ModelClassCreatorDefine<T>|null;
}

type ModelClassCreatorDefine<T> = (new () => T);

export interface RequestCacheConfig {
  /**
   * 缓存保存时间，毫秒。超过时间后再请求时会发请求
   */
  cacheTime: number,
  /**
   * 是否启用缓存
   */
  cacheEnable: boolean,
}

export interface RequestCacheStorage {
  time: number,
  data: TypeSaveable
}

export class RequestOptions {
  /**
   * 请求的参数
   */
  data?: string | object | ArrayBuffer | FormData;
  /**
  * 设置请求的 header，header 中不能设置 Referer。
  */
  header?: any;
  /**
  * 默认为 GET
  * 可以是：OPTIONS，GET，HEAD，POST，PUT，DELETE，TRACE，CONNECT
  */
  method?: 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | 'CONNECT';
  /**
  * 超时时间
  */
  timeout?: number;
  /**
  * 如果设为json，会尝试对返回的数据做一次 JSON.parse
  */
  dataType?: string;
  /**
  * 设置响应的数据类型。合法值：text、arraybuffer
  */
  responseType?: string;
  /**
  * 验证 ssl 证书
  */
  sslVerify?: boolean;
  /**
  * 跨域请求时是否携带凭证
  */
  withCredentials?: boolean;
  /**
  * DNS解析时优先使用 ipv4
  */
  firstIpv4?: boolean;
}
/**
 * API 请求核心实例类，本类是对 fetch 的封装，提供了基本的请求功能。
 */
export class RequestCoreInstance<T extends DataModel> {

  constructor(implementer: RequestImplementer) {
    this.implementer = implementer;
  }

  /**
   * 当前请求实例的请求配置项
   */
  config : RequestCoreConfig<T> = {
    baseUrl: '',
    errCodes: {},
    timeout: 10000,
    defaultHeader: RequestApiConfig.getConfig().DefaultHeader as HeaderType,
    modelClassCreator: null,
    responseDataHandler: defaultResponseDataHandler,
    responseErrorHandler: defaultResponseErrorHandler,
  };

  /**
   * 请求实现类
   */
  implementer: RequestImplementer;

  /**
   * 检查是否需要报告错误
   */
  checkShouldReportError(err: RequestApiError) {
    if (typeof this.config.responseErrReoprtInceptor === 'function')
      return this.config.responseErrReoprtInceptor(this, err) !== true;
    return true;
  }
  /**
   * 报告错误
   * @param err 错误
   */
  reportError(err: RequestApiError|Error) {
    if (this.checkShouldReportError(err as RequestApiError)) {
      if (typeof this.config.reportError === 'function')
        this.config.reportError(this, err);
    }
  }
  /**
   * 在配置中查找错误代码的说明文字
   * @param code 错误代码
   * @returns 说明文字，如果找不到，返回 undefined
   */
  findErrCode(code: number) : string|undefined {
    return this.config.errCodes[code];
  }

  /**
   * 合并URL
   */
  makeUrl(url: string, querys?: QueryParams) {
    let finalUrl = '';
    if (url.indexOf('http') === 0)
      finalUrl = url; //绝对地址
    else
      finalUrl = this.config.baseUrl + url;
    //处理query
    if (querys) {
      let i = finalUrl.indexOf('?') > 0 ? 1 : 0;
      for (const key in querys) {
        if (typeof querys[key] === 'undefined' || querys[key] === null)
          continue;
        finalUrl += i === 0 ? '?' : '&';
        if (typeof querys[key] === 'object')
          finalUrl += `${key}=` + encodeURIComponent(JSON.stringify(querys[key]));
        else
          finalUrl += `${key}=` + '' + querys[key];
        i++;
      }
    }
    return finalUrl;
  }
  //合并默认Header参数
  private mergerDefaultHeader(header: Record<string, unknown>) {
    const myHeaders = {} as Record<string, unknown>;
    for (const key in this.config.defaultHeader)
      myHeaders[key] = this.config.defaultHeader[key];
    if (header) {
      for (const key in header) 
        myHeaders[key] = header[key];
    }
    return myHeaders;
  }
  /**
   * 合并两个Header参数
   * @param header 合并目标
   * @param newHeader 新的Header
   * @returns 合并后的Header
   */
  mergerHeaders(header: Record<string, unknown>, newHeader: Record<string, unknown>) {
    if (!newHeader)
      return header;
    if (!header)
      return newHeader;
    for (const key in newHeader)
      header[key] = newHeader[key];
    return header;
  }

  //检查缓存参数
  private checkCacheTime(cache?: RequestCacheConfig) {
    return cache && cache.cacheEnable && cache.cacheTime || 0;
  }
  //请求缓存处理
  private solveCache(url: string, req: RequestOptions, cache: RequestCacheConfig|undefined, callback: (cacheTime: number, cacheKey: string, res: TypeSaveable) => void) {
    const cacheTime = req.method === 'GET' ? this.checkCacheTime(cache) : 0;
    let requestHash = '';
    if (cacheTime > 0) {
      requestHash = "RequestCache" + stringHashCode(url + req.method);
      //获取数据
      this.implementer.getCache(requestHash).then((cacheData) => {
        if (!cacheData) {
          callback(cacheTime, requestHash, null);
          return;
        }
        //没有过期
        if (cacheData.time < new Date().getTime()) {
          callback(cacheTime, requestHash, cacheData.time);
          return;
        }
        callback(cacheTime, requestHash, null);
      }).catch(() => {
        callback(cacheTime, requestHash, null);
      }); 
    } else
      callback(cacheTime, requestHash, null);
  }

  /**
   * 通用的请求包装方法
   * @param url 请求URL
   * @param req 请求参数
   * @param apiName 名称，用于日志和调试
   * @returns 返回 Promise
   */
  request(url: string, req: RequestOptions,  apiName: string, modelClassCreator: NewDataModel|undefined, cache?: RequestCacheConfig) : Promise<RequestApiResult<T>> {
    return new Promise<RequestApiResult<T>>((resolve, reject) => {
      //附加请求头
      req.header = this.mergerDefaultHeader(req.header);
      
      //拦截器
      if (this.config.requestInceptor) {
        const { newUrl, newReq } = this.config.requestInceptor(url, req);
        url = newUrl;
        req = newReq;
      }
      if (req.data instanceof FormData) {
        req.header['Content-Type'] = 'multipart/form-data';
      } else if (typeof req.data === 'object' || req.data === undefined) {
        req.header['Content-Type'] = 'application/json';
      }

      if (RequestApiConfig.getConfig().EnableApiRequestLog)
        console.log(`[API Debugger] Q > ${apiName} [${req.method || 'GET'}] ` + url, req.data);

      //缓存处理
      this.solveCache(url, req, cache, (cacheTime, cacheKey, cacheRes) => {

        //有缓存数据，则直接返回
        if (cacheRes) {
          if (RequestApiConfig.getConfig().EnableApiRequestLog)
            console.log(`[API Debugger] C > ${apiName} (${cacheKey}/${cacheTime})`, ( RequestApiConfig.getConfig().EnableApiDataLog ? cacheRes.toString() : ''));
          resolve(cacheRes as unknown as RequestApiResult<T>);
          return;
        }

        //发送请求并且处理响应数据
        this.requestAndResponse(url, req, apiName, modelClassCreator, (result) => {
          //保存缓存
          if (cacheTime > 0) {
            this.implementer.setCache(cacheKey, {
              time: new Date().getTime() + cacheTime,
              data: result as unknown as TypeSaveable,
            });
          }
        }).then((d) => {
          resolve(d);
        }).catch((e) => {
          reject(e);
        });
      });
    });
  }

  //发送请求并且处理
  private requestAndResponse(url: string, req: RequestOptions, apiName: string, resultModelClass: NewDataModel|undefined, saveCache?: (result: unknown) => void) {
    return new Promise<RequestApiResult<T>>((resolve, reject) => {
      //发起请求
      this.implementer.doRequest(url, req, this.config.timeout).then((res) => {
        //响应拦截
        if (this.config.responseInceptor)
          res = this.config.responseInceptor(res);

        if (this.config.responseDataHandler) {
          //处理数据
          this.config.responseDataHandler(res, req, resultModelClass, this, apiName).then((result) => {
            //尝试保存缓存
            saveCache && saveCache(result);
            //处理数据
            try {
              if (RequestApiConfig.getConfig().EnableApiRequestLog)
                console.log(`[API Debugger] R > ${apiName} (${res.status}/${result.code})`);
              //返回
              resolve(result);
            } catch (e) {
              //捕获处理代码的异常
              console.error('[API Debugger] E > Catch exception in promise : ' + e + ((e as Error).stack ? ('\n' + (e as Error).stack) : ''));
              reject(new RequestApiError('scriptError', '代码异常，请检查：' + e, '脚本异常', -1, null, e as unknown as KeyValue, req, apiName));
            }
          }).catch((e) => {
            reject(e);
          });
        }
        else
          reject(new RequestApiError('scriptError', 'This RequestCoreInstance is not configured with responsedatahandler and cannot convert data! ', '脚本异常', -1, null, null, req, apiName));
      }).catch((err) => {
        reject(this.config.responseErrorHandler ? this.config.responseErrorHandler(err, this, apiName) : err);
      });
    });
  }

  /**
   * GET 请求
   * @param url 请求URL
   * @param querys 请求URL参数
   * @param cache 缓存参数
   */
  get(url: string, apiName: string, querys?: QueryParams, modelClassCreator?: NewDataModel, cache?: RequestCacheConfig, headers?: KeyValue) {
    return this.request(this.makeUrl(url, querys), { method: 'GET', header: headers }, apiName, modelClassCreator, cache);
  }
  /**
   * POST 请求
   * @param url 请求URL
   * @param data 请求Body参数
   * @param querys 请求URL参数
   * @param cache 缓存参数
   */
  post(url: string, data: KeyValue|FormData, apiName: string, querys?: QueryParams, modelClassCreator?: NewDataModel, cache?: RequestCacheConfig, headers?: KeyValue) {
    return this.request(this.makeUrl(url, querys), { method: 'POST', data, header: headers }, apiName, modelClassCreator, cache);
  }
  /**
   * PUT 请求
   * @param url 请求URL
   * @param data 请求Body参数
   * @param querys 请求URL参数
   * @param cache 缓存参数
   */
  put(url: string, data: KeyValue, apiName: string,querys?: QueryParams,  modelClassCreator?: NewDataModel, cache?: RequestCacheConfig, headers?: KeyValue) {
    return this.request(this.makeUrl(url, querys), { method: 'PUT', data, header: headers }, apiName, modelClassCreator, cache);
  }
  /**
   * DELETE 请求
   * @param url 请求URL
   * @param data 请求Body参数
   * @param querys 请求URL参数
   * @param cache 缓存参数
   */
  delete(url: string, data: KeyValue, apiName: string, querys?: QueryParams, modelClassCreator?: NewDataModel, cache?: RequestCacheConfig, headers?: KeyValue) {
    return this.request(this.makeUrl(url, querys), { method: 'DELETE', data, header: headers }, apiName, modelClassCreator, cache);
  }
}
