#include "pch.h"

#include "tunnel.h"
#include "protocol.h"

#include "globalcfg.h"
#include "httplib.h"
#include "usrerr.h"

#include <strsafe.h>
#include <spdlog/fmt/bin_to_hex.h>

#define HTTP_JSON_CONTENT TEXT("application/json")

static httplib::Client *g_httpCtx       = nullptr;
static httplib::Client *g_tunnelHttpCtx = nullptr;

int InitControlServer(const TCHAR *pUserSvrUrl) {

    if (g_tunnelHttpCtx) {
        delete g_tunnelHttpCtx;
        g_tunnelHttpCtx = nullptr;
    }

    if (UsedSCGProxy()) {
        TCHAR scgProxyUrl[MAX_PATH];
        StringCbPrintf(scgProxyUrl, MAX_PATH, TEXT("http://127.0.0.1:%d"), GetGlobalCfgInfo()->scgProxy.scgGwPort);

        SPDLOG_DEBUG(TEXT("Control Server Used Proxy: {0} --> {1}"), pUserSvrUrl, scgProxyUrl);
        g_tunnelHttpCtx = new httplib::Client(scgProxyUrl);
    } else {
        g_tunnelHttpCtx = new httplib::Client(pUserSvrUrl);
        SPDLOG_DEBUG(TEXT("Control Server Unused Proxy: {0}"), pUserSvrUrl);
    }

    if (g_tunnelHttpCtx) {
        g_tunnelHttpCtx->set_connection_timeout(0, 1000000);    // 1 second
        g_tunnelHttpCtx->set_read_timeout(5, 0);                // 5 seconds
        g_tunnelHttpCtx->set_write_timeout(5, 0);               // 5 seconds
        g_tunnelHttpCtx->set_post_connect_cb([](socket_t sock) {
            if (UsedSCGProxy()) {
                int                ret;
                unsigned char      vmid[4];
                unsigned char     *p;
                const unsigned int id         = htonl(GetGlobalCfgInfo()->curConnVmId);
                const auto         svrId      = static_cast<UINT8>(GetGlobalCfgInfo()->userCfg.cliConfig.scgCtrlAppId);
                unsigned char      scgProxy[] = {0x01,      // VERSION
                                                 0x09,      // Length
                                                 0xF0,      // ++++++ INFO[0] TYPE
                                                 0x04,      //        INFO[0] LENGTH
                                                 0,         //        INFO[0] VMID[0]
                                                 0,         //        INFO[0] VMID[1]
                                                 0,         //        INFO[0] VMID[2]
                                                 0,         //        INFO[0] VMID[3]
                                                 0xF1,      //        INFO[1] TYPE
                                                 0x01,      //        INFO[1] LENGTH
                                                 svrId};    // ------ INFO[1] SCG Service ID

                p = scgProxy;
                memcpy(vmid, &id, 4);
                scgProxy[4] = vmid[0];
                scgProxy[5] = vmid[1];
                scgProxy[6] = vmid[2];
                scgProxy[7] = vmid[3];

                if (GetGlobalCfgInfo()->logLevel == spdlog::level::trace) {
                    std::array<unsigned char, sizeof(scgProxy)> arr;
                    std::copy(std::begin(scgProxy), std::end(scgProxy), std::begin(arr));
                    SPDLOG_DEBUG(TEXT("TCP Proxy SCG Payload: {0:Xa}"), spdlog::to_hex(arr));
                }

                ret = send(sock, reinterpret_cast<const char *>(p), sizeof(scgProxy), 0);

                while (ret < static_cast<int>(sizeof(scgProxy))) {
                    p   += ret;
                    ret += send(sock, reinterpret_cast<const char *>(p), sizeof(scgProxy), 0);
                }

                SPDLOG_DEBUG(TEXT("Service Connected To SCG Server({1}/{2}): {0}"),
                             sock,
                             GetGlobalCfgInfo()->curConnVmId,
                             svrId);
            }
        });
    }

    SPDLOG_DEBUG(TEXT("Connect to Tunnel Control Service: {0}"), pUserSvrUrl);

    return ERR_SUCCESS;
}

template<class T> int CreateProtocolRequest(T *pReqParams, TCHAR **pOutJson) {
    std::string json;

    if (!g_httpCtx && lstrlen(GetGlobalCfgInfo()->platformServerUrl) > 0) {
        g_httpCtx = new httplib::Client(GetGlobalCfgInfo()->platformServerUrl);
        if (g_httpCtx) {
            g_httpCtx->set_connection_timeout(0, 300000);    // 300 milliseconds
            g_httpCtx->set_read_timeout(5, 0);               // 5 seconds
            g_httpCtx->set_write_timeout(5, 0);              // 5 seconds
        }
    }

    if (aigc::JsonHelper::ObjectToJson(*pReqParams, json)) {
        *pOutJson = _strdup(json.c_str());
        return ERR_SUCCESS;
    }

    return -ERR_JSON_CREATE;
}

template<class T> int DecodeProtocolResponse(T *pResponse, const TCHAR *pJson) {
    if (aigc::JsonHelper::JsonToObject(*pResponse, pJson)) {
        return ERR_SUCCESS;
    }

    return -ERR_JSON_DECODE;
}

template<class T1, class T2>
int ProtolPostMessage(const TCHAR          *pUrlPath,
                      ProtocolRequest<T1>  *pReq,
                      ProtocolResponse<T2> *pRsp,
                      bool                  platformServer) {
    int             ret;
    httplib::Result res;
    TCHAR          *pJson = nullptr;

    if (lstrlen(GetGlobalCfgInfo()->platformServerUrl) == 0) {
        SPDLOG_ERROR(TEXT("Platform Server URL uninitialize."));
        return -ERR_SYSTEM_UNINITIALIZE;
    }

    if (pReq == nullptr) {
        SPDLOG_ERROR(TEXT("Input pToken params error"));
        SPDLOG_ERROR(TEXT("Input ProtocolRequest<T1> *pReq params error"));
        return -ERR_INPUT_PARAMS;
    }

    if (pRsp == nullptr) {
        SPDLOG_ERROR(TEXT("Input ProtocolResponse<T2> *pRsp params error"));
        return -ERR_INPUT_PARAMS;
    }

    ret = CreateProtocolRequest(pReq, &pJson);

    if (ret != ERR_SUCCESS) {
        if (pJson) {
            free(pJson);
        }
        return ret;
    }

    if (platformServer) {
        std::string timestamp           = std::to_string(time(nullptr)) + "000";
        TCHAR       hashValeu[MAX_PATH] = {0};
        TCHAR       hashBuf[1024]       = {};

        StringCbPrintf(hashBuf,
                       1024,
                       TEXT("%s|%s|%s|%s"),
                       GetGlobalCfgInfo()->clientId,
                       GetGlobalCfgInfo()->clientSecret,
                       timestamp.c_str(),
                       pJson);

        if (lstrlen(GetGlobalCfgInfo()->clientSecret) > 0 &&
            CalcHmacHash(HASH_SHA256,
                         reinterpret_cast<PUCHAR>(hashBuf),
                         lstrlen(hashBuf),
                         reinterpret_cast<PUCHAR>(GetGlobalCfgInfo()->clientSecret),
                         lstrlen(GetGlobalCfgInfo()->clientSecret),
                         hashValeu,
                         true) == ERR_SUCCESS) {
            const httplib::Headers headers = {
                {"gzs-client-id", GetGlobalCfgInfo()->clientId},
                {"gzs-sign",      hashValeu                   },
                {"gzs-timestamp", timestamp                   },
            };
            res = g_httpCtx->Post(pUrlPath, headers, pJson, HTTP_JSON_CONTENT);
        } else {
            res = g_httpCtx->Post(pUrlPath, pJson, HTTP_JSON_CONTENT);
        }
    } else {
        if (g_tunnelHttpCtx == nullptr) {
            free(pJson);
            SPDLOG_ERROR(TEXT("Server Control Service don't connected(g_tunnelHttpCtx is not initialize)."));
            return -ERR_SYSTEM_UNINITIALIZE;
        }
        res = g_tunnelHttpCtx->Post(pUrlPath, pJson, HTTP_JSON_CONTENT);
    }

    if (res.error() != httplib::Error::Success) {
        SPDLOG_ERROR(TEXT("[{0}]:Post Data {1} error: {2}"), pUrlPath, pJson, httplib::to_string(res.error()));
        free(pJson);
        return -ERR_HTTP_POST_DATA;
    }

    if (res->status != 200) {
        SPDLOG_ERROR(TEXT("[{0}]:Post Data {1} server return HTTP error: {2}"), pUrlPath, pJson, res->status);
        free(pJson);
        return -ERR_HTTP_SERVER_RSP;
    }

    SPDLOG_DEBUG(TEXT("+++++ Http Request {0}\n---- Http Response {1}"), pJson, res->body.c_str());

    free(pJson);

    if (lstrlen(res->body.c_str()) == 0) {
        SPDLOG_ERROR(TEXT("Server response empty message"));
        return -ERR_READ_FILE;
    }

    if (DecodeProtocolResponse(pRsp, res->body.c_str()) != ERR_SUCCESS) {
        SPDLOG_ERROR(TEXT("Decode JSON {0} to ProtocolResponse<{1}> error"), res->body, typeid(T2).name());
        return -ERR_JSON_DECODE;
    }

    return ERR_SUCCESS;
}

#if 0
template<class T1> int PlatformProtolGetMessage(const TCHAR *pUrlPath, T1 *pRsp) {
    httplib::Result res;
    TCHAR          *pJson               = nullptr;
    std::string     timestamp           = std::to_string(time(nullptr)) + "000";
    TCHAR           hashValeu[MAX_PATH] = {0};
    TCHAR           hashBuf[1024]       = {};

    if (lstrlen(GetGlobalCfgInfo()->platformServerUrl) == 0) {
        SPDLOG_ERROR(TEXT("Platform Server URL uninitialize."));
        return -ERR_SYSTEM_UNINITIALIZE;
    }

    if (pRsp == nullptr) {
        SPDLOG_ERROR(TEXT("Input ProtocolResponse<T2> *pRsp params error"));
        return -ERR_INPUT_PARAMS;
    }

    StringCbPrintf(hashBuf,
                   1024,
                   TEXT("%s|%s|%s|%s"),
                   GetGlobalCfgInfo()->clientId,
                   GetGlobalCfgInfo()->clientSecret,
                   timestamp.c_str(),
                   pJson);

    if (lstrlen(GetGlobalCfgInfo()->clientSecret) > 0 &&
        CalcHmacHash(HASH_SHA256,
                     reinterpret_cast<PUCHAR>(hashBuf),
                     lstrlen(hashBuf),
                     reinterpret_cast<PUCHAR>(GetGlobalCfgInfo()->clientSecret),
                     lstrlen(GetGlobalCfgInfo()->clientSecret),
                     hashValeu,
                     true) == ERR_SUCCESS) {
        const httplib::Headers headers = {
            {"gzs-client-id", GetGlobalCfgInfo()->clientId},
            {"gzs-sign",      hashValeu                   },
            {"gzs-timestamp", timestamp                   },
        };
        res = g_httpCtx->Get(pUrlPath, headers);
    } else {
        res = g_httpCtx->Get(pUrlPath);
    }

    if (res.error() != httplib::Error::Success) {
        SPDLOG_ERROR(TEXT("[{0}]:Post Data {1} error: {2}"), pUrlPath, pJson, httplib::to_string(res.error()));
        free(pJson);
        return -ERR_HTTP_POST_DATA;
    }

    if (res->status != 200) {
        SPDLOG_ERROR(TEXT("[{0}]:Post Data {1} server return HTTP error: {2}"), pUrlPath, pJson, res->status);
        free(pJson);
        return -ERR_HTTP_SERVER_RSP;
    }

    SPDLOG_DEBUG(TEXT("+++++ Http Request {0}\n---- Http Response {1}"), pJson, res->body.c_str());

    free(pJson);

    if (lstrlen(res->body.c_str()) == 0) {
        SPDLOG_ERROR(TEXT("Server response empty message"));
        return -ERR_READ_FILE;
    }

    if (DecodeProtocolResponse(pRsp, res->body.c_str()) != ERR_SUCCESS) {
        SPDLOG_ERROR(TEXT("Decode JSON {0} to ProtocolResponse<{1}> error"), res->body, typeid(T1).name());
        return -ERR_JSON_DECODE;
    }

    return ERR_SUCCESS;
}
#endif

template<class T1, class T2> int PlatformProtolPostMessage(const TCHAR *pUrlPath, T1 *pReq, T2 *pRsp) {
    int             ret;
    httplib::Result res;
    TCHAR          *pJson               = nullptr;
    std::string     timestamp           = std::to_string(time(nullptr)) + "000";
    TCHAR           hashValeu[MAX_PATH] = {0};
    TCHAR           hashBuf[1024]       = {};

    if (lstrlen(GetGlobalCfgInfo()->platformServerUrl) == 0) {
        SPDLOG_ERROR(TEXT("Platform Server URL uninitialize."));
        return -ERR_SYSTEM_UNINITIALIZE;
    }

    if (pReq == nullptr) {
        SPDLOG_ERROR(TEXT("Input pToken params error"));
        SPDLOG_ERROR(TEXT("Input ProtocolRequest<T1> *pReq params error"));
        return -ERR_INPUT_PARAMS;
    }

    if (pRsp == nullptr) {
        SPDLOG_ERROR(TEXT("Input ProtocolResponse<T2> *pRsp params error"));
        return -ERR_INPUT_PARAMS;
    }

    ret = CreateProtocolRequest(pReq, &pJson);

    if (ret != ERR_SUCCESS) {
        if (pJson) {
            free(pJson);
        }
        return ret;
    }

    StringCbPrintf(hashBuf,
                   1024,
                   TEXT("%s|%s|%s|%s"),
                   GetGlobalCfgInfo()->clientId,
                   GetGlobalCfgInfo()->clientSecret,
                   timestamp.c_str(),
                   pJson);

    if (lstrlen(GetGlobalCfgInfo()->clientSecret) > 0 &&
        CalcHmacHash(HASH_SHA256,
                     reinterpret_cast<PUCHAR>(hashBuf),
                     lstrlen(hashBuf),
                     reinterpret_cast<PUCHAR>(GetGlobalCfgInfo()->clientSecret),
                     lstrlen(GetGlobalCfgInfo()->clientSecret),
                     hashValeu,
                     true) == ERR_SUCCESS) {

        if (lstrcmp(typeid(T1).name(), TEXT("class PlatformReqClientCfgParms")) == 0) {
            const auto            *p       = reinterpret_cast<PlatformReqClientCfgParms *>(pReq);
            const httplib::Headers headers = {
                {"gzs-client-id", GetGlobalCfgInfo()->clientId},
                {"gzs-sign",      hashValeu                   },
                {"gzs-timestamp", timestamp                   },
                {"gzs-userid",    p->token.c_str()            },
            };

            res = g_httpCtx->Post(pUrlPath, headers, pJson, HTTP_JSON_CONTENT);
        } else {
            const httplib::Headers headers = {
                {"gzs-client-id", GetGlobalCfgInfo()->clientId},
                {"gzs-sign",      hashValeu                   },
                {"gzs-timestamp", timestamp                   },
            };

            res = g_httpCtx->Post(pUrlPath, headers, pJson, HTTP_JSON_CONTENT);
        }
    } else {
        if (lstrcmp(typeid(T1).name(), TEXT("class PlatformReqClientCfgParms")) == 0) {
            const auto            *p       = reinterpret_cast<PlatformReqClientCfgParms *>(pReq);
            const httplib::Headers headers = {
                {"gzs-userid", p->token.c_str()},
            };
            res = g_httpCtx->Post(pUrlPath, headers, pJson, HTTP_JSON_CONTENT);
        } else {
            res = g_httpCtx->Post(pUrlPath, pJson, HTTP_JSON_CONTENT);
        }
    }

    if (res.error() != httplib::Error::Success) {
        SPDLOG_ERROR(TEXT("[{0}]:Post Data {1} error: {2}"), pUrlPath, pJson, httplib::to_string(res.error()));
        free(pJson);
        return -ERR_HTTP_POST_DATA;
    }

    if (res->status != 200) {
        SPDLOG_ERROR(TEXT("[{0}]:Post Data {1} server return HTTP error: {2}"), pUrlPath, pJson, res->status);
        free(pJson);
        return -ERR_HTTP_SERVER_RSP;
    }

    SPDLOG_DEBUG(TEXT("+++++ Http Request {0}\n---- Http Response {1}"), pJson, res->body.c_str());

    free(pJson);

    if (lstrlen(res->body.c_str()) == 0) {
        SPDLOG_ERROR(TEXT("Server response empty message"));
        return -ERR_READ_FILE;
    }

    if (DecodeProtocolResponse(pRsp, res->body.c_str()) != ERR_SUCCESS) {
        SPDLOG_ERROR(TEXT("Decode JSON {0} to ProtocolResponse<{1}> error"), res->body, typeid(T2).name());
        return -ERR_JSON_DECODE;
    }

    return ERR_SUCCESS;
}

template int ProtolPostMessage(const TCHAR                            *pUrlPath,
                               ProtocolRequest<ReqGetUserCfgParams>   *pReq,
                               ProtocolResponse<RspUserSevrCfgParams> *pRsp,
                               bool                                    platformServer);

template int ProtolPostMessage(const TCHAR                             *pUrlPath,
                               ProtocolRequest<ReqGetUserCfgParams>    *pReq,
                               ProtocolResponse<RspUsrCliConfigParams> *pRsp,
                               bool                                     platformServer);

template int ProtolPostMessage(const TCHAR                              *pUrlPath,
                               ProtocolRequest<ReqUserSetCliCfgParams>  *pReq,
                               ProtocolResponse<RspUserSetCliCfgParams> *pRsp,
                               bool                                      platformServer);

template int ProtolPostMessage(const TCHAR                           *pUrlPath,
                               ProtocolRequest<ReqStartTunnelParams> *pReq,
                               ProtocolResponse<ResponseStatus>      *pRsp,
                               bool                                   platformServer);

template int ProtolPostMessage(const TCHAR                      *pUrlPath,
                               ProtocolRequest<ReqHeartParams>  *pReq,
                               ProtocolResponse<RspHeartParams> *pRsp,
                               bool                              platformServer);

#if !USER_REAL_PLATFORM
template int PlatformProtolPostMessage(const TCHAR                *pUrlPath,
                                       PlatformReqServerCfgParms  *pReq,
                                       PlatformRspServerCfgParams *pRsp);

template int PlatformProtolPostMessage(const TCHAR                *pUrlPath,
                                       PlatformReqClientCfgParms  *pReq,
                                       PlatformRspClientCfgParams *pRsp);

//template int PlatformProtolGetMessage(const TCHAR *pUrlPath, PlatformRspUserClientCfgParams *pRsp);
#endif