#include "http_request.h"

#include <library/cpp/logger/global/global.h>
#include <library/cpp/openssl/io/stream.h>

#include <library/cpp/http/io/stream.h>
#include <library/cpp/json/json_reader.h>

#include <util/datetime/base.h>
#include <util/network/socket.h>

namespace NUtil {

    bool SendRequest(const TString& host, ui16 port, const TString& data, const TDuration& timeout, THttpReply& result, bool isHttps) {
        result.SetIsConnected(false);
        result.SetIsCorrectReply(false);
        result.SetErrorMessage("");
        try {
            TSocket s(TNetworkAddress(host, port), timeout);
            s.SetSocketTimeout(1 + timeout.Seconds());
            TSocketOutput so(s);
            if (isHttps) {
                TSocketInput si(s);
                TOpenSslClientIO ssl(&si, &so);
                THttpOutput ho(&ssl);
                ho << data;
                ho.Finish();
                THttpInput hi(&ssl);
                result.SetCode(ParseHttpRetCode(hi.FirstLine()));
                result.SetContent(hi.ReadAll());
            } else {
                so << data;
                so.Flush();
                TSocketInput si(s);
                THttpInput hi(&si);
                result.SetCode(ParseHttpRetCode(hi.FirstLine()));
                result.SetContent(hi.ReadAll());
            }
            result.SetIsConnected(true);
            result.SetIsCorrectReply(true);
            return true;
        }
        catch (...) {
            result.SetErrorMessage(CurrentExceptionMessage());
            return false;
        }
    }

    void THttpReply::Deserialize(const NJson::TJsonValue& value) {
        IsConnected_ = value["is_connected"].GetBooleanRobust();
        IsCorrectReply_ = value["is_correct_reply"].GetBooleanRobust();
        Code_ = value["code"].GetIntegerRobust();
        Content_ = value["content"].GetStringRobust();
        const auto it = value.GetMap().find("error");
        if (it != value.GetMap().end())
            ErrorMessage_ = it->second.GetStringRobust();
    }

    NJson::TJsonValue THttpReply::Serialize() const {
        NJson::TJsonValue result(NJson::JSON_MAP);
        result.InsertValue("code", Code_);
        if (!!ErrorMessage_)
            result.InsertValue("error", ErrorMessage_);
        result.InsertValue("is_connected", IsConnected_);
        result.InsertValue("is_correct_reply", IsCorrectReply_);
        NJson::TJsonValue contentJson;
        TStringInput si(Content_);
        if (NJson::ReadJsonTree(&si, &contentJson)) {
            result.InsertValue("content", std::move(contentJson));
        }
        else {
            result.InsertValue("content", Content_);
        }
        return result;
    }

    THttpReply THttpRequest::Execute(const TString& host, ui32 port) {
        DEBUG_LOG << "actor=http_request;receiver=" << host << ":" << port << ";command=" << Command << ";status=starting;" << Endl;
        TStringStream ss;

        if (!PostData) {
            ss << "GET /" << Command << " HTTP/1.1\r\n";
            ss << "Host:" << host << "\r\n";

            for (const auto& header : AdditionHeaders) {
                ss << header.first << ":" << header.second << "\r\n";
            }
            ss << "\r\n";
        } else {
            ss << "POST /" << Command << " HTTP/1.1\r\n";
            for (const auto& header : AdditionHeaders) {
                ss << header.first << ":" << header.second << "\r\n";
            }
            ss << "Content-Length:" << PostData.size() << "\r\n";
            if (!AdditionHeaders.contains("Content-Type")) {
                ss << "Content-Type:application/octet-stream\r\n";
            }
            ss << "Host:" << host << "\r\n";
            ss << "\r\n";
            ss << PostData;
        }
        THttpReply result;
        for (ui32 i = 0; i < SendAttemptionsMax; ++i) {
            if (SendRequest(host, port, ss.Str(), Timeout, result, IsHttps)) {
                if (RetryOn5xx && result.IsServerError()) {
                    ERROR_LOG << "actor=controller_agent;receiver=" << host << ":" << port << ";command=" << Command << ";status=failed;code=" << result.Code() << ";attemption=" << i << Endl;
                    Sleep(SleepingPause);
                } else {
                    DEBUG_LOG << "actor=controller_agent;receiver=" << host << ":" << port << ";command=" << Command << ";status=finished;code=" << result.Code() << Endl;
                    break;
                }
            } else {
                ERROR_LOG << "actor=controller_agent;receiver=" << host << ":" << port << ";command=" << Command << ";status=failed;msg=" << result.ErrorMessage() << ";attemption=" << i << Endl;
                Sleep(SleepingPause);
            }
        }
        return result;
    }
}
