#include "multipart_adapter.h"

#include <saas/api/factors_erf.h>
#include <saas/indexerproxy/adapters/json/json_adapter.h>
#include <saas/indexerproxy/context/action_reply.h>
#include <saas/indexerproxy/context/document.h>

#include <dict/web/multipart.h>
#include <dict/web/utils.h>

#include <util/string/split.h>
#include <util/string/vector.h>


using namespace NJson;
using namespace NIndexerProxy;

const TString NIndexerProxy::TAdapterRTYServer::AdapterAlias("rtyserver");

namespace {
    inline TString GetString(const TJsonValue& value, const char* errorMsg = nullptr) {
        if (value.IsString())
            return value.GetString();
        else if (value.IsInteger())
            return ToString(value.GetInteger());
        else
            ythrow yexception() << errorMsg;
    }

    inline bool GetBoolean(const TJsonValue& value, const char* errorMsg = nullptr) {
        if (value.IsBoolean())
            return value.GetBoolean();
        else if (value.IsString())
            return FromString<bool>(value.GetString());
        else
            ythrow yexception() << errorMsg;
    }

    inline long long GetInt(const TJsonValue& value, const char* errorMsg = nullptr) {
        if (value.IsInteger())
            return value.GetInteger();
        else if (value.IsString())
            return FromString<long long>(value.GetString());
        else
            ythrow yexception() << errorMsg;
    }

    inline double GetDouble(const TJsonValue& value, const char* errorMsg = nullptr) {
        if (value.IsInteger())
            return value.GetInteger();
        else if (value.IsDouble())
            return value.GetDouble();
        else if (value.IsString())
            return FromString<double>(value.GetString());
        else
            ythrow yexception() << errorMsg;
    }
}

#define CHECK_STRING(i) \
    TString value;\
    try {\
        value = GetString(i->second);\
    } catch(...) {\
        errorMessage = "Incorrect " + i->first + " value type";\
        return false;\
    }

#define CHECK_BOOL(i) \
    bool value;\
    try {\
        value = GetBoolean(i->second);\
    } catch(...) {\
        errorMessage = "Incorrect " + i->first + " value type";\
        return false;\
    }

#define CHECK_INT(i) \
    long long value;\
    try {\
        value = GetInt(i->second);\
    } catch(...) {\
        errorMessage = "Incorrect " + i->first + " value type";\
        return false;\
    }

#define CHECK_DOUBLE(i) \
    double value;\
    try {\
        value = GetDouble(i->second);\
    } catch(...) {\
        errorMessage = "Incorrect " + i->first + " value type";\
        return false;\
    }

#define CHECK_ARRAY(i) \
    if (!i->second.IsArray()) {\
        errorMessage = "Incorrect " + i->first + " value type";\
        return false;\
    }\
    const TJsonValue::TArray& value = i->second.GetArray();

bool TAdapterRTYServer::ParseMessage(ISenderTask& context, TString& body, TString& json) const {
    if (!context.GetContext().GetPost().Length())
        return false;
    TString ss(context.GetContext().GetPost().AsCharPtr(), context.GetContext().GetPost().Length());
    TContentTypeHeader contentType;
    for (ui32 i = 0; i < context.GetContext().GetRD().HeadersCount(); i++) {
        ParseContentType(context.GetContext().GetRD().HeaderByIndex(i), &contentType);
        if (contentType.Boundary != "")
            break;
    }
    if (contentType.Boundary != "") {
        TMultipartReader mpr(ss, contentType.Boundary);
        bool kJson = false;
        bool kBody = false;
        while (mpr.ReadNext()) {
            if (mpr.GetName() == "document") {
                body = mpr.GetValue();
                if (kBody)
                    RETURN_ERROR("more that one 'document' part")
                kBody = true;
            }
            if (mpr.GetName() == "json_message") {
                json = mpr.GetValue();
                if (kJson)
                    RETURN_ERROR("more that one 'json_message' part")
                kJson = true;
            }
        }
        if (!kJson)
            RETURN_ERROR("cannot read 'json_message' part")
        return true;
    } else {
        RETURN_ERROR("it is not multipart http")
    }
}

bool TAdapterRTYServer::ParseDocumentEntities(NRTYServer::TMessage& message, const NJson::TJsonValue& jSON, TString& errorMessage) const {
    if (jSON.IsMap()) {
        message.MutableDocument()->SetKeyPrefix(0);
        const NJson::TJsonValue::TMapType& mapJSON = jSON.GetMap();
        for (NJson::TJsonValue::TMapType::const_iterator i = mapJSON.begin(), e = mapJSON.end(); i != e; ++i) {
            if (!ReadMapElement(message, i, errorMessage)) {
                return false;
            }
        }
        const TRTYMessageBehaviour& bechavior = GetBehaviour(message.GetMessageType());
        if (bechavior.IsBroadcastMessage)
            message.MutableDocument()->SetUrl(bechavior.Name);
    } else {
        errorMessage = "Incorrect json element type";
        return false;
    }
    return true;
}

bool TAdapterRTYServer::ReadAttributes(NRTYServer::TMessage& message, const TString& attributesType, const TJsonValue::TArray& array, TString& errorMessage) const {
    for (TJsonValue::TArray::const_iterator i = array.begin(), e = array.end(); i != e; ++i) {
        if (!i->IsMap()) {
            errorMessage = "Incorrect attributes array element";
            return false;
        } else {
            const TJsonValue::TMapType& map = i->GetMap();
            TJsonValue::TMapType::const_iterator iName = map.find("name");
            TJsonValue::TMapType::const_iterator iValue = map.find("value");
            TJsonValue::TMapType::const_iterator iType = map.find("type");
            TString value;
            TString name;
            TString type;
            try {
                if (iName == map.end() || iValue == map.end())
                    ythrow yexception();
                name = GetString(iName->second);
                value = GetString(iValue->second);
            } catch(...) {
                errorMessage = "Incorrect attribute map describing";
                return false;
            }

            NRTYServer::TAttribute::TAttributeType protoType = NRTYServer::TAttribute::LITERAL_ATTRIBUTE; // to make gcc happy

            if (attributesType == "search_attributes")
                protoType = NRTYServer::TAttribute::LITERAL_ATTRIBUTE;
            else if (attributesType == "document_attributes")
                protoType = NRTYServer::TAttribute::LITERAL_ATTRIBUTE;
            else if (attributesType == "grouping_attributes")
                protoType = NRTYServer::TAttribute::INTEGER_ATTRIBUTE;
            else if (attributesType == "factors")
                Y_UNUSED(protoType);
            else {
                errorMessage = "incorrect attribute type (search, document, grouping or factor)";
                return false;
            }

            if ((iType != map.end()) && (iType->second.GetString(&type))) {
                if (type == "integer")
                    protoType = NRTYServer::TAttribute::INTEGER_ATTRIBUTE;
                else if (type == "literal")
                    protoType = NRTYServer::TAttribute::LITERAL_ATTRIBUTE;
                else if (type == "float")
                    Y_UNUSED(protoType);
                else {
                    errorMessage = "incorrect attribute type " + type + ", must be (integer|float|literal)";
                    return false;
                }
            }

            NRTYServer::TAttribute* attr = nullptr;
            if (attributesType == "search_attributes") {
                if (protoType != NRTYServer::TAttribute::LITERAL_ATTRIBUTE &&
                    protoType != NRTYServer::TAttribute::INTEGER_ATTRIBUTE)
                {
                    errorMessage = "incorrect search attribute type (literal and int only)";
                    return false;
                }
                attr = message.MutableDocument()->AddSearchAttributes();
                attr->set_type(protoType);
                attr->set_name(name);
                attr->set_value(value);
                TString type;
            } else if (attributesType == "document_attributes") {
                NRTYServer::TMessage::TDocument::TProperty* prop = message.MutableDocument()->AddDocumentProperties();
                prop->set_name(name);
                prop->set_value(value);
                continue;
            } else if (attributesType == "grouping_attributes") {
                if (protoType != NRTYServer::TAttribute::INTEGER_ATTRIBUTE)
                {
                    errorMessage = "incorrect grouping attribute type (int only)";
                    return false;
                }
                attr = message.MutableDocument()->AddGroupAttributes();
                attr->set_type(protoType);
                attr->set_name(name);
                attr->set_value(value);
            } else if (attributesType == "factors") {
                NSaas::AddSimpleFactor(name, value, *message.MutableDocument()->MutableFactors());
            } else {
                errorMessage = "Incorrect type of attributes";
                return false;
            }
        }
    }
    return true;
}

bool TAdapterRTYServer::ReadMapElement(NRTYServer::TMessage& message, TJsonValue::TMapType::const_iterator& i, TString& errorMessage) const {
    if (i->first == "search_attributes" || i->first == "factors" || i->first == "grouping_attributes" || i->first == "document_attributes") {
        CHECK_ARRAY(i);
        if (!ReadAttributes(message, i->first, value, errorMessage)) {
            return false;
        }
    } else
    if (i->first == "send_type") {
        CHECK_STRING(i);
        if (!GetBehaviour(value)) {
            errorMessage = "send_type value is incorrect \"" + value + "\"";
            return false;
        }
        message.SetMessageType(GetBehaviour(value)->MessageType);
    }
    else if (i->first == "url") {
        CHECK_STRING(i);
        message.MutableDocument()->SetUrl(value);
        if (value == "") {
            errorMessage = "Incorrect json element url value";
            return false;

        }
    }
    else if (i->first == "keyprefix") {
        CHECK_INT(i);
        message.MutableDocument()->SetKeyPrefix(value);
        if (value < 0) {
            errorMessage = "Incorrect json element keyprefix value";
            return false;

        }
    }
    else if (i->first == "deadline_min_utc") {
        CHECK_INT(i);
        message.MutableDocument()->SetDeadlineMinutesUTC(value);
        if (value < 0) {
            errorMessage = "Incorrect deadline_min_utc element value";
            return false;
        }
    }
    else if (i->first == "version") {
        CHECK_INT(i);
        message.MutableDocument()->SetVersion(value);
        if (value < 0) {
            errorMessage = "Incorrect version element value";
            return false;
        }
    }
    else if (i->first == "filter_rank") {
        CHECK_DOUBLE(i);
        message.MutableDocument()->SetFilterRank(value);
    }
    else if (i->first == "realtime") {
        CHECK_BOOL(i);
        message.MutableDocument()->SetRealtime(value);
    }
    else if (i->first == "check_only_before_reply") {
        CHECK_BOOL(i);
        message.MutableDocument()->SetCheckOnlyBeforeReply(value);
    }
    else if (i->first == "mime_type") {
        CHECK_STRING(i);
        message.MutableDocument()->SetMimeType(value);
        if (value == "") {
            errorMessage = "Incorrect mime_type element value";
            return false;
        }
    }
    else if (i->first == "charset") {
        CHECK_STRING(i);
        message.MutableDocument()->SetCharset(value);
    }
    else if (i->first == "language") {
        CHECK_STRING(i);
        message.MutableDocument()->SetLanguage(value);
    }
    else if (i->first == "language2") {
        CHECK_STRING(i);
        message.MutableDocument()->SetLanguage2(value);
    }
    else if (i->first == "modification_timestamp") {
        CHECK_INT(i);
        message.MutableDocument()->SetModificationTimestamp(value);
    }
    else if (i->first == "message_id") {
        CHECK_INT(i);
        message.SetMessageId(value);
    }
    else {
        errorMessage = "Incorrect json element \"" + i->first + "\"";
        return false;
    }
    return true;
};

static TAtomic __counter = 0;

ui64 TAdapterRTYServer::GenerateMessageId() {
    return AtomicIncrement(__counter);
}

bool TAdapterRTYServer::ParseMessage(ISenderTask& context) const {
    TString body, json;

    TAutoPtr<TDispatchableDocument> doc = new TDispatchableDocument(context);

    if (!ParseMessage(context, body, json))
        return false;

    if (!!body)
        doc->MutableMessage()->MutableDocument()->SetBody(body);

    TStringInput ss(json);
    NJson::TJsonValue outJSON;
    if (!NJson::ReadJsonTree(&ss, &outJSON)) {
        RETURN_ERROR("Can't parse json");
    }

    TString errorStr;
    if (!ParseDocumentEntities(*doc->MutableMessage(), outJSON, errorStr)) {
        RETURN_ERROR("Can't parse message(" + errorStr + ")");
    }

    if (!doc->GetMessage().HasMessageId() && context.GetContext().GetRequestOptions().GetWaitReply())
        doc->MutableMessage()->SetMessageId(GenerateMessageId());

    context.GetContext().GetDocument().Reset(doc.Release());

    return true;
}

TAdaptersFactory::TRegistrator<NIndexerProxy::TAdapterRTYServer> NIndexerProxy::TAdapterRTYServer::Registrator(NIndexerProxy::TAdapterRTYServer::AdapterAlias);
