#include <search/web/core/rule.h>

#include <search/meta/context.h>

#include <saas/util/json/json.h>
#include <saas/searchproxy/common/cgi.h>

#include <library/cpp/json/json_value.h>

#include <util/string/vector.h>
#include <util/generic/algorithm.h>

#include <kernel/qtree/richrequest/serialization/serializer.h>


using namespace NJson;

namespace {
    const char Delimiter[] = ",";
    const char CgiParamFormatDelimiter = '@';

    const TSet<TString> SearchPropPrefixes = {
        "facet_"
    };

    struct TRTYSearchPropertiesRuleData {
        TMaybe<TString> Ctype;
    };
}

class TRTYSearchPropertiesRuleContext: public IRearrangeRuleContext {
public:
    const TRTYSearchPropertiesRuleData& Data;

    TRTYSearchPropertiesRuleContext(const TRTYSearchPropertiesRuleData& data)
        : Data(data)
    {}

    virtual ~TRTYSearchPropertiesRuleContext() {}

    void Defaults() {
        JoinSearchPropsByPrefix = SearchPropPrefixes;
        DumpCgiParams.clear();
        DumpCgiParams.insert(NSearchProxyCgi::ClickLogging, NSearchProxyCgi::ClickLogging + Y_ARRAY_SIZE(NSearchProxyCgi::ClickLogging));
    }

    void ParseConfiguration() {
        const NSc::TValue& l = LocalScheme();
        const TVector<TString>& cgiParams = SplitString(l["DumpCgiParams"].GetString().data(), Delimiter);
        const TVector<TString>& join = SplitString(l["JoinSearchProps"].GetString().data(), Delimiter);
        const TVector<TString>& joinByPrefix = SplitString(l["JoinSearchPropsByPrefix"].GetString().data(), Delimiter);
        DumpRequest = l["DumpRequest"].IsTrue();
        DumpHeaders = l["DumpHeaders"].IsTrue();

        for (const TString& cgiParam : cgiParams) {
            const TStringBuf paramDescription = cgiParam;
            NSearchProxyCgi::TClickLogField field = {
                TString{paramDescription.Before(CgiParamFormatDelimiter)},
                TString{paramDescription.After(CgiParamFormatDelimiter)}
            };
            DumpCgiParams.insert(field);
        }
        Copy(join.begin(), join.end(), inserter(JoinSearchProps, JoinSearchProps.end()));
        Copy(joinByPrefix.begin(), joinByPrefix.end(), inserter(JoinSearchPropsByPrefix, JoinSearchPropsByPrefix.end()));
    }

    TJsonValue GetCgiParams(TRearrangeParams& rearrangeParams) {
        TJsonValue result(JSON_MAP);
        const TCgiParameters& cgi = rearrangeParams.GetRequestCgi();
        for (const auto& field: DumpCgiParams) {
            const size_t num = cgi.NumOfValues(field.CgiParam);
            if (num == 1) {
                result.InsertValue(field.Remap, cgi.Get(field.CgiParam));
            } else if (num > 1) {
                TJsonValue arr(JSON_ARRAY);
                for (size_t i = 0; i < num; ++i)
                    arr.AppendValue(cgi.Get(field.CgiParam, i));
                result.InsertValue(field.Remap, arr);
            }
        }
        return result;
    }

    TJsonValue GetProperties(TRearrangeParams& rearrangeParams) {
        TJsonValue result(JSON_MAP);

        const TSearcherProps& searcherProps = rearrangeParams.GetProperties();

        for (TSet<TString>::const_iterator i = JoinSearchProps.begin(); i != JoinSearchProps.end(); ++i) {
            const TString& prop = *i;
            TString value;
            if (searcherProps.Get(prop, value))
                result.InsertValue(prop, value);
        }

        for (TSet<TString>::const_iterator i = JoinSearchPropsByPrefix.begin(); i != JoinSearchPropsByPrefix.end(); ++i)
            for (TSearcherProps::const_iterator prop = searcherProps.begin(); prop != searcherProps.end(); ++prop)
        {
            if (prop->first.StartsWith(*i))
                result.InsertValue(prop->first, prop->second);
        }

        return result;
    }

    void DoRearrangeAfterFetch(TRearrangeParams& rearrangeParams) override {
        if (LocalScheme()["Disable"].IsTrue())
            return;

        if (LocalScheme()["UseDefaults"].IsTrue()) {
            Defaults();
        }
        ParseConfiguration();

        if (Data.Ctype) {
            rearrangeParams.InsertWorkedRule("ctype", *Data.Ctype);
        }

        const TJsonValue& cgi = GetCgiParams(rearrangeParams);
        rearrangeParams.InsertWorkedRule("Cgi.nodump", NUtil::JsonToString(cgi));

        const TJsonValue& props = GetProperties(rearrangeParams);
        rearrangeParams.InsertWorkedRule("FoldedSearchProps.nodump", NUtil::JsonToString(props));

        if (DumpRequest) {
            const TString& wizardedRequest = rearrangeParams.RP.GetParsedRequest();
            rearrangeParams.Context()->SetProperty("rty_request", wizardedRequest);
        }
        if (DumpHeaders) {
            if (auto rd = rearrangeParams.Context()->RequestData) {
                for (size_t i = 0; i < rd->HeadersCount(); ++i) {
                    auto header = SplitString(rd->HeaderByIndex(i), ": ");
                    rearrangeParams.InsertWorkedRule("Header_" + header[0] + ".nodump", (header.size() > 1) ? header[1] : "");
                }
            }
        }

        if (rearrangeParams.GetRequestCgi().Has("rty_qtree", "true")) {
            Y_ENSURE(rearrangeParams.RP.HasRequestTree());
            const auto requestTreePtr = rearrangeParams.RP.GetRequestTree();
            TString buffer;
            TRichTreeSerializer{}.SerializeBase64(*requestTreePtr, buffer);
            rearrangeParams.Context()->SetProperty("rty_qtree", buffer);
        }
    }

private:
    TSet<NSearchProxyCgi::TClickLogField> DumpCgiParams;
    TSet<TString> JoinSearchProps;
    TSet<TString> JoinSearchPropsByPrefix;
    bool DumpRequest;
    bool DumpHeaders;
};

struct TRTYSearchPropertiesRule: IRearrangeRule {
    TRTYSearchPropertiesRuleData Data;

    TRTYSearchPropertiesRule(TString options) {
        const NRearrConf::TRearrangeRuleParams params(options);
        TString ctype = TString{params.Value("ctype", TStringBuf(""))};
        if (ctype) {
            Data.Ctype = ctype;
        }
    }

    IRearrangeRuleContext* DoConstructContext() const override {
        return new TRTYSearchPropertiesRuleContext(Data);
    }
};

IRearrangeRule* CreateRTYPropertiesRule(const TString& options, const TSearchConfig&) {
    const NRearrConf::TRearrangeRuleParams params(options);

    return new TRTYSearchPropertiesRule(options);
}

REGISTER_REARRANGE_RULE(RTYProperties, CreateRTYPropertiesRule);
