#include "socket_adapter_proto2http.h"
#include <library/cpp/json/json_writer.h>
#include <library/cpp/json/json_reader.h>
#include <util/stream/str.h>
#include <util/string/vector.h>
#include <util/string/split.h>
#include <library/cpp/http/io/stream.h>
#include <saas/library/behaviour/behaviour.h>
#include <library/cpp/logger/global/global.h>
#include <library/cpp/http/multipart/send.h>

typedef ::google::protobuf::RepeatedPtrField< ::NRTYServer::TAttribute > TAttrsProto;
typedef ::google::protobuf::RepeatedPtrField< ::NRTYServer::TMessage::TDocument::TProperty > TPropsProto;
typedef ::NRTYServer::TMessage::TErfInfo TRTYErfInfoProto;
typedef ::NRTYServer::TMessage::TFactorValues TRTYFactorValues;

namespace {
    NJson::TJsonValue BuildAttrsList(const TAttrsProto& attrs) {
        NJson::TJsonValue result;
        result.SetType(NJson::JSON_ARRAY);
        for (TAttrsProto::const_iterator i = attrs.begin(), e = attrs.end(); i != e; ++i) {
            NJson::TJsonValue attr;
            NJson::TJsonValue name(i->name());
            NJson::TJsonValue value(i->value());
            TString typeStr;
            switch (i->type()) {
                case NRTYServer::TAttribute::INTEGER_ATTRIBUTE:
                    typeStr = "integer";
                    break;
                case NRTYServer::TAttribute::LITERAL_ATTRIBUTE:
                    typeStr = "literal";
                    break;
                default:
                    Y_FAIL("Incorrect usage");
            }
            NJson::TJsonValue type(typeStr);
            attr.InsertValue("name", name);
            attr.InsertValue("value", value);
            attr.InsertValue("type", type);
            result.AppendValue(attr);
        }
        return result;
    }

    NJson::TJsonValue BuildPropsList(const TPropsProto& props) {
        NJson::TJsonValue result;
        result.SetType(NJson::JSON_ARRAY);
        for (TPropsProto::const_iterator i = props.begin(), e = props.end(); i != e; ++i) {
            NJson::TJsonValue attr;
            NJson::TJsonValue name(i->name());
            NJson::TJsonValue value(i->value());
            attr.InsertValue("name", name);
            attr.InsertValue("value", value);
            result.AppendValue(attr);
        }
        return result;
    }

    NJson::TJsonValue BuildFactorsList(const TRTYErfInfoProto& factors) {
        NJson::TJsonValue result(NJson::JSON_ARRAY);
        for (size_t i = 0; i != factors.NamesSize(); ++i) {
            NJson::TJsonValue factor;
            factor.InsertValue("name", factors.GetNames(i));
            factor.InsertValue("type", "float");
            const TRTYFactorValues::TValue& erfValue = factors.GetValues().GetValues(i);
            switch (erfValue.GetAction()) {
                case TRTYFactorValues::SET :
                    factor.InsertValue("value", ToString(erfValue.GetValue()));
                    break;
                case TRTYFactorValues::ADD :
                    factor.InsertValue("value", "add:" + ToString(erfValue.GetValue()));
                    break;
                default:
                    FAIL_LOG("Unknown factor action");
            }
            result.AppendValue(factor);
        }
        return result;
    }
}

TSocketAdapterProto2Http::TSocketAdapterProto2Http(const TString& service, IInputStream& input, IOutputStream& output, bool realtime)
: ISocketAdapter(input, output)
, Service(service)
, UsedToWrite(false)
, UsedToRead(false)
, Realtime(realtime)
{}

TString TSocketAdapterProto2Http::BuildJson(const NRTYServer::TMessage& message) const {
    NJson::TJsonValue root(NJson::JSON_MAP);
    TString type = "";
    type = GetBehaviour(message.GetMessageType()).Name;

    root.InsertValue("send_type", type);
    if (message.HasMessageId())
        root.InsertValue("message_id", message.GetMessageId());
    if (message.HasDocument()) {
        if (message.GetDocument().HasUrl())
            root.InsertValue("url", message.GetDocument().GetUrl());
        if (message.GetDocument().HasKeyPrefix())
            root.InsertValue("keyprefix", message.GetDocument().GetKeyPrefix());
        if (message.GetDocument().HasMimeType())
            root.InsertValue("mime_type", message.GetDocument().GetMimeType());
        if (message.GetDocument().HasCharset())
            root.InsertValue("charset", message.GetDocument().GetCharset());
        if (message.GetDocument().HasLanguage())
            root.InsertValue("language", message.GetDocument().GetLanguage());
        if (message.GetDocument().HasLanguage2())
            root.InsertValue("language2", message.GetDocument().GetLanguage2());
        if (message.GetDocument().HasModificationTimestamp())
            root.InsertValue("modification_timestamp", message.GetDocument().GetModificationTimestamp());
        if (message.GetDocument().HasDeadlineMinutesUTC())
            root.InsertValue("deadline_min_utc", message.GetDocument().GetDeadlineMinutesUTC());
        if (message.GetDocument().HasVersion())
            root.InsertValue("version", message.GetDocument().GetVersion());
        if (message.GetDocument().HasRealtime())
            root.InsertValue("realtime", message.GetDocument().GetRealtime());

        NJson::TJsonValue searchAttrs = BuildAttrsList(message.GetDocument().GetSearchAttributes());
        NJson::TJsonValue groupAttrs = BuildAttrsList(message.GetDocument().GetGroupAttributes());
        NJson::TJsonValue props = BuildPropsList(message.GetDocument().GetDocumentProperties());
        NJson::TJsonValue factors = BuildFactorsList(message.GetDocument().GetFactors());

        if (searchAttrs.GetArray().size())
            root.InsertValue("search_attributes", searchAttrs);
        if (groupAttrs.GetArray().size())
            root.InsertValue("grouping_attributes", groupAttrs);
        if (props.GetArray().size())
            root.InsertValue("document_attributes", props);
        if (factors.GetArray().size())
            root.InsertValue("factors", factors);
    }
    TStringStream ss;
    NJson::WriteJson(&ss, &root);
    return ss.Str();
}

void TSocketAdapterProto2Http::WriteMessage(const ::google::protobuf::Message& message, const TString& request) const {
    Y_VERIFY(!UsedToWrite, "TSocketAdapterProto2Http: only one message to socket");
    UsedToWrite = true;
    const NRTYServer::TMessage& messageRTY = dynamic_cast<const NRTYServer::TMessage&>(message);
    MessageId = messageRTY.GetMessageId();
    TVector<std::pair<const char*, TString> > parts;
    parts.push_back(std::make_pair("document", messageRTY.GetDocument().GetBody()));
    parts.push_back(std::make_pair("json_message", BuildJson(messageRTY)));
    Output << BuildMultipartHttpRequest(parts, "/service/" + Service + "?ftests=yes" + (Realtime ? "" : "&realtime=no") + request, "");
    Output.Flush();
}

void TSocketAdapterProto2Http::WriteMessage(const TString& message, const TString& request) const {
    Y_VERIFY(!UsedToWrite, "TSocketAdapterProto2Http: only one message to socket");
    UsedToWrite = true;
    MessageId = 1;
    TString result = "POST /service/" + Service + request + " HTTP/1.1\r\n";
    result += "Content-Type: text/plain\r\n";
    result += "Content-Length: " + ToString<size_t>(message.size()) + "\r\n";
    result += message + "\r\n";
    Output << result;
    Output.Flush();
}

bool TSocketAdapterProto2Http::ReadMessage(::google::protobuf::Message& message) const {
    Y_VERIFY(!UsedToRead, "TSocketAdapterProto2Http: only one message from socket");
    UsedToRead = true;
    NRTYServer::TReply& replyRTY = dynamic_cast<NRTYServer::TReply&>(message);
    THttpInput input(&Input);
    replyRTY.SetMessageId(MessageId);
    int httpCode = ParseHttpRetCode(input.FirstLine());
    try {
        const TString msg(input.ReadAll());
        replyRTY.SetStatusMessage(msg);
        NJson::TJsonValue reply;
        TStringInput si(msg);
        NJson::ReadJsonTree(&si, &reply);
        const NJson::TJsonValue& docReply = reply["dispatch"][0];
        bool wasDataAccepted = false;
        int rtyStatus = -1;
        NJson::TJsonValue rtyMsg(NJson::JSON_MAP);
        NJson::TJsonValue oneRtyMsg;
        for(NJson::TJsonValue::TMapType::const_iterator i = docReply.GetMap().begin(); i != docReply.GetMap().end(); ++i)
            if (i->second.IsMap()) {
                rtyStatus = FromString<int>(i->second["RTYStatus"].GetString());
                if (rtyStatus == NRTYServer::TReply::DATA_ACCEPTED)
                    wasDataAccepted = true;
                TStringStream ss;
                ss << i->second["RTYMessage"].GetString();
                NJson::ReadJsonTree(&ss, &oneRtyMsg);
                rtyMsg.InsertValue(i->first, oneRtyMsg);
            }
        if (rtyStatus != -1){
            replyRTY.SetStatus(wasDataAccepted ? NRTYServer::TReply::DATA_ACCEPTED : rtyStatus);
            TStringStream ss;
            NJson::WriteJson(&ss, &rtyMsg);
            if (wasDataAccepted && reply.Has("async_code"))
                replyRTY.SetStatusMessage(reply["async_code"].GetString());
            else
                replyRTY.SetStatusMessage(ss.Str());
            DEBUG_LOG << "rtyStatus: " << rtyStatus << " rtyMessage: " << ss.Str() << "\n";
        }
    } catch (...) {
        replyRTY.SetStatus(DecodeHttpToDisp(httpCode));
        replyRTY.SetStatusMessage(ToString(httpCode));
    }
    return true;
}
