import {ServiceClient} from './ServiceClient'
import {AuthType} from "./enums/AuthType";
import {HttpMethodType} from "./enums/HttpMethodType";
import {RequestBodyType} from "./enums/RequestBodyType";
import {IHttpResponse} from "./utils/http/IHttpResponse";
import {SimpleHttpUtil} from "./utils/http/SimpleHttpUtil";
import {MsFile} from "./model/MsFile";
import {GatewayConfig} from "./config/GatewayConfig";
import {ServiceClientConfig} from "../core/config/ServiceClientConfig";
import {StcHttpRequestObj} from "./model/StcHttpRequestObj";
import sideCar from "./sidecar/SideCar";
import UuidUtil from "./utils/UuidUtil";
import IPUtil from "./utils/IPUtil";
import {MsConsts} from "./defines/MsConsts";



/**
 * 微服务客户端使用接口
 * 微服务主地址、appId、appKey -> 配置文件中设置
 * traceId -> 客户端内部处理，从当前request的header获取，不可为空，如果当前request无值，那么客户端生成一个uuid填入本次调用的header
 * @author WuFenglin
 **/
export class ServiceClientImpl implements ServiceClient {

    private gatewayConfig: GatewayConfig = GatewayConfig.make();
    private serviceClientConfig: ServiceClientConfig;
    private simpleHttpUtil: SimpleHttpUtil;

    constructor(serviceClientConfig: ServiceClientConfig) {
        this.serviceClientConfig = serviceClientConfig;
        this.simpleHttpUtil = new SimpleHttpUtil(this.serviceClientConfig)
    }

    beforeMsCall(sid: string, version: string, requestObj: StcHttpRequestObj) {
        let parentCallId = "";
        let traceId = UuidUtil.get();
        let callId = UuidUtil.get();
        let callLink = `[${IPUtil.getLocalIP()}]`;

        const header = requestObj.header;
        if (header) {
            header[MsConsts.PARENT_CALL_ID_KEY] = parentCallId;
            header[MsConsts.TRACE_ID_KEY] = traceId;
            header[MsConsts.CALL_ID_KEY] = callId;
            header[MsConsts.SPAN_ID_KEY] = callId;
            header[MsConsts.CALL_LINK_KEY] = callLink;
        }
        if (this.serviceClientConfig.httpClientHook != null) {
            this.serviceClientConfig.httpClientHook.beforeMsCall(sid, version, requestObj);
        }
    }

    afterMsCall(sid: string, version: string, requestObj?: StcHttpRequestObj, result?: IHttpResponse) {
        this.serviceClientConfig.httpClientHook?.afterMsCall(sid, version, requestObj, result)
    }

    addCallLog(beginTime: number, requestObj?: StcHttpRequestObj, result?: IHttpResponse) {
    }

    async call<T = any, R = IHttpResponse<T>>(httpMethod: HttpMethodType, sid: string, version: string, tags: string, header: object, params: object, bodyObj: object, authType: AuthType, bodyType: RequestBodyType): Promise<R> {
        let requestBodyObj = bodyObj;
        if (bodyType == RequestBodyType.WWW_FORM) {
            requestBodyObj = params
        }
        return this.sendRequest(httpMethod, sid, version, tags, header, params, requestBodyObj, authType, bodyType)
    }

    async get<T = any, R = IHttpResponse<T>>(sid: string, version: string, tags: string, header: object, params: object, authType: AuthType): Promise<R> {
        return this.sendRequest(HttpMethodType.GET, sid, version, tags, header, params, {}, authType, RequestBodyType.WWW_FORM)
    }


    postFiles<T = any, R = IHttpResponse<T>>(sid: string, version: string, tags: string, header: object, params: object, files: Array<MsFile>, authType: AuthType): Promise<R> {
        let bodyObj = params || {}
        return this.sendRequest(HttpMethodType.UPLOAD_FILE, sid, version, tags, header, params, bodyObj, authType, RequestBodyType.WWW_FORM, files)
    }


    private async sendRequest<T = any, R = IHttpResponse<T>>(method: HttpMethodType, sid: string, version: string, tags: string, header: object
        , params: object, bodyObj: object, authType: AuthType, bodyType: RequestBodyType, files?: Array<MsFile>): Promise<R> {
        const beginTime = this.currentTimeMillis();
        let result: IHttpResponse<R> | undefined;
        let requestObj: StcHttpRequestObj | undefined;
        try {
            this.gatewayConfig.check();
            const signMethod = method == HttpMethodType.UPLOAD_FILE ? HttpMethodType.POST : method
            requestObj = new StcHttpRequestObj(this.gatewayConfig, signMethod, sid, version, tags, params, header, bodyObj, authType, bodyType);
            const url = sideCar.buildHostUrl(this.gatewayConfig, sid, version, requestObj);
            requestObj.callUrl = url
            this.beforeMsCall(sid, version, requestObj);
            switch (method) {
                case HttpMethodType.GET:
                    result = await this.simpleHttpUtil.get(url, requestObj.params, requestObj.header);
                    break;
                case HttpMethodType.UPLOAD_FILE:
                    if (files != null) {
                        result = await this.simpleHttpUtil.uploadFiles(url, requestObj.params, files, requestObj.header);
                    } else {
                        throw new Error('Upload Error files is not empty...')
                    }
                    break
                default :
                    result = await this.simpleHttpUtil.call(method, url, requestObj.params, bodyObj, requestObj.header, bodyType);
                    break;
            }

            return result.data;
        } finally {
            this.addCallLog(beginTime, requestObj, result)
            this.afterMsCall(sid, version, requestObj, result);
        }
    }


    private currentTimeMillis() {
        return new Date().getTime()
    }


}