#include "metasearch.h"

#include <saas/rtyserver/config/config.h>
#include <saas/rtyserver/config/searcher_config.h>
#include <saas/rtyserver/search/factory/factory.h>
#include <saas/library/metasearch/cgi/meta_cgi_filter.h>

#include <search/meta/context.h>
#include <search/web/core/rearrange.h>

namespace NRTYServer {

namespace {

struct TCgiHashHelper {
    static TRequestHash AppendHash(TRequestHash crc, const TSet<TString>& keys, const TCgiParameters& cgi) {
        // see also: search/common/cphash.cpp
        // This method differs from TCpHashFunctor in a significant way: when a new item
        // is added to RTYMetaCgiParamsToBase, we should not change the hash of any existing request,
        // unless the new parameter is used.
        auto reqHash = [](const TString& item) -> TRequestHash {
            return CityHash64(item.data(), item.size());
        };

        for (const TString& key: keys) {
            const auto range = cgi.Range(key);
            if (range.empty())
                continue;

            crc = CombineHashes(crc, ~reqHash(key));
            for (const auto& value: range)
                crc = CombineHashes(crc, reqHash(value));
        }
        return crc;
    }

    static TRequestHash CPConnHash(const TCgiParameters& cgi) {
        return AppendHash(::CPConnHash(cgi), TRTYMetaCgiParams::GetParamsForHash(), cgi);
    }

    static TRequestHash CPMetaHash(const TCgiParameters& cgi) {
        // we do not include RTYMetaCgiParamsToBackend or RTYMetaCgiParamsToService here
        // this means &ag= and &ag0= are not included
        return AppendHash(::CPMetaHash(cgi), TRTYMetaCgiParams::GetParamsForHash(), cgi);
    }
};


class TMetaSearchContext : public ::TMetaSearchContext {
public:
    using TBase = ::TMetaSearchContext;
public:
    TMetaSearchContext(const TMetaSearch& owner, TAtomicSharedPtr<IExternalSearchCreator> extsearch, bool askFactorsOnSearchStage)
        : ::TMetaSearchContext(owner)
        , ExternalSearchCreator(extsearch)
        , AskFactorsOnSearchStage(askFactorsOnSearchStage)
    {
        if (ExternalSearchCreator) {
            MutableRP().ExternalCgi = ExternalSearchCreator->CreateMetaCgi(RP(), owner.GetGlobalConfig());
            MutableRP().GroupingAliasing.Forbidden = true; // SAAS-4258
        }
    }

    void SetCgiParamsForClient() override {
        // inproc:// does not support compression
        MutableRP().RequestCompression = NMetaProtocol::CM_COMPRESSION_NONE;

        ::TMetaSearchContext::SetCgiParamsForClient();

        auto kpsIt = CgiParam.find("kps");
        if (kpsIt != CgiParam.end()) {
            ClientCgiParams.InsertUnescaped("rbkps", kpsIt->second);
        }
    }

    void SetupRequestParams() override {
        EnableLoadLog = !IsTrue("skiploadlog");
        ::TMetaSearchContext::SetupRequestParams();
    }

    TRequestHash CalcRequestHash() const override {
        TRequestHash crc = TBase::CalcRequestHash();
        return TCgiHashHelper::AppendHash(crc, TRTYMetaCgiParams::GetParamsForHash(), CgiParam);
    }

    TString ExtraCgiForSearchStage() const override {
        return AskFactorsOnSearchStage || AreRtyFactorsRequested() ? "&allfctrs=da" : "";
    }

    bool ForceReportAllFactors() const override {
        return AskFactorsOnSearchStage || AreRtyFactorsRequested();
    }
private:
    // TODO: should be moved to AskFactorsOnSearchStage after search refactor
    bool AreRtyFactorsRequested() const {
        auto fsgtaFactors = CgiParam.Range("fsgta");
        return std::any_of(fsgtaFactors.begin(), fsgtaFactors.end(), [&](const auto& item)->bool { return item == "_RtyFactors"; });
    }

private:
    const TAtomicSharedPtr<IExternalSearchCreator> ExternalSearchCreator;
    const bool AskFactorsOnSearchStage;
};

struct TRTYMetaParams : public TMetaSearch::TParams {
    THolder<IMetaRearrange> ConstructRearrange(const TConstructRearrangeEnv& env) override {
        return WebRearrangeCore(env);
    }
};


} // namespace

TMetaSearch::TMetaSearch(const TRTYServerConfig& globalConfig)
    : ::TMetaSearch(Singleton<TRTYMetaParams>())
    , GlobalConfig(globalConfig)
    , ExternalSearchCreator(NRTYServer::IExternalSearchCreator::TFactory::Construct(globalConfig.GetSearcherConfig().ExternalSearch))
{
}

TMetaSearch::~TMetaSearch() {
}

ISearchContext* TMetaSearch::CreateContext() const {
    return new TMetaSearchContext(*this, ExternalSearchCreator, GlobalConfig.GetSearcherConfig().AskFactorsOnSearchStage);
}

ISearchContext* TMetaSearch::CreateContext(const TSearchRequestData&) const {
    return CreateContext();
}

int TMetaSearch::SearchOpen(TAutoPtr<TSearchConfig> config) {
    if (config) {
        Y_ASSERT(!config->HashFunction && !config->MetaHashFunction); // TSearchConfigBuilderMeta never sets these fields
        config->HashFunction = &TCgiHashHelper::CPConnHash;
        config->MetaHashFunction = &TCgiHashHelper::CPMetaHash;
    }
    return SearchOpen(config, nullptr);
}

} // namespace NRTYServer
