#include "replier.h"

#include <saas/rtyserver/unistat_signals/signals.h>
#include <saas/rtyserver/common/metrics.h>
#include <saas/rtyserver/config/searcher_config.h>
#include <saas/rtyserver/logging/rty_access.h>
#include <saas/rtyserver/search/search_engines_manager.h>

#include <search/grouping/groupinfo.h>
#include <search/memory/rtsearchmetrics.h>
#include <search/request/data/reqdata.h>
#include <search/supermind/supermind.h>
#include <search/session/comsearch.h>

#include <util/string/type.h>

void TRTYCommonSearchReplier::ScanPruningOptions() {
    const TCgiParameters& cgi = Context->GetRequestData().CgiParam;

    const auto range = cgi.equal_range("pron");
    for (auto it = range.first; it != range.second; ++it) {
        for (const auto& onePron: StringSplitter(it->second).Split(';').SkipEmpty()) {
            const auto token = onePron.Token();
            ManualPruning |= (token == "prune");
            ManualPruning |= token.StartsWith("pruncount");
            DisablePruning |= (token =="noprune");
        }
    }
}

bool TRTYCommonSearchReplier::NeedPruningAdjust() {
    return !DisablePruning && !ManualPruning;
}

void TRTYCommonSearchReplier::AdjustRTYRequest() {
    const TRTYServerConfig& cfg = SEM.GetConfig();
    TCgiParameters& cgi = Context->MutableRequestData().CgiParam;
    if (!cfg.GetSearcherConfig().UseRTYExtensions) {
        return;
    }

    ScanPruningOptions();

    if (ManualPruning && !cfg.Pruning->PruningOn())
        ythrow yexception() << "pruning conflict";
    TString pruningAttr = cfg.Pruning->GetType() == TPruningConfig::GROUP_ATTR ? cfg.Pruning->ToString() : TString();
    TGroupingParams grouping;
    const ui64 groupingsCount = cgi.NumOfValues("g");
    if (groupingsCount) {
        grouping.Parse(cgi.Get("g").data());
    } else {
        grouping.gMode = GM_FLAT;
        grouping.gAttrToSort = cgi.Get("how");
        grouping.gAscendOrder = IsTrue(cgi.Get("asc"));
    }
    if (!!pruningAttr && NeedPruningAdjust()) {
        if (grouping.gAttrToSort == pruningAttr && !grouping.gAscendOrder && groupingsCount < 2) {
            const ui32 gCoeff = grouping.gMode != GM_FLAT ? cfg.GetSearcherConfig().GroupPruningCoefficient : 1;
            ui32 numdoc = gCoeff * grouping.gGroups * grouping.gDocs;
            const TString& pageStr = cgi.Get("p");
            const ui64 page = (!!pageStr) ? (FromString<ui64>(pageStr) +1) : 1;
            numdoc = page * numdoc;
            cgi.InsertUnescaped("pron", "prune");
            cgi.InsertUnescaped("pron", "pruncount" + ToString(numdoc));
        } else {
            cgi.InsertUnescaped("pron", "noprune");
        }
    }
    const ui64 defKps = DefaultKps;
    if (cfg.IsPrefixedIndex && defKps) {
        const TString& kps = cgi.Get("kps");
        ui64 cgiKps = !!kps ? FromString<ui64>(kps) : 0;
        if (!cgiKps)
            cgi.InsertUnescaped("kps", ToString(defKps));
    }
}

bool TRTYCommonSearchReplier::ProcessRequest() {
    THolder<NMemorySearch::TResponseTimeGuard> timeGuard = CreateTimeGuard();
    TSimpleSharedPtr<TSuperMind::TRequest> smRequest(new TSuperMind::TRequest(TSuperMind::TRequest::Search, Context->GetRequestStartTime().MicroSeconds()));
    TSimpleSharedPtr<TSuperMind::TRequestContext> smRequestContext(GetSuperMind()->Register(smRequest));

    AdjustRTYRequest();

    const TInstant start = Now();
    const bool result = TCommonSearchReplier::ProcessRequest();
    const TInstant finish = Now();

    TRTYAccessLog::LogSearch(Context->GetRequestId(), finish - Context->GetRequestStartTime(), finish - start, result, Stat.TotalDocsCount, Stat.ReportDocsCount, Stat.ReportByteSize, Stat.CacheHit, Stat.UnanswerCount, CommonRequestType, Stat.FastCacheHit, false, static_cast<i32>(Stat.AnswerIsComplete), Stat.SearchErrors);
    return result;
}

TDuration TRTYCommonSearchReplier::GetDefaultTimeout() const {
    return SEM.GetConfig().GetSearcherConfig().GetScatterTimeout();
}

void TRTYCommonSearchReplier::OnRequestExpired(const int /*httpCode*/) {
    NRTYServer::TCommonReplierFeatures::OnRequestExpired(Context, SEM.GetMainSearchServerMetrics());
}

void TRTYCommonSearchReplier::OnQueueFailure() {
    NRTYServer::TCommonReplierFeatures::OnQueueFailure(Context, SEM.GetMainSearchServerMetrics());
    ISearchReplier::OnQueueFailure();
}

bool TRTYCommonSearchReplier::ProcessFetch() {
    THolder<NMemorySearch::TResponseTimeGuard> timeGuard = CreateTimeGuard();
    TSimpleSharedPtr<TSuperMind::TRequest> smRequest(new TSuperMind::TRequest(TSuperMind::TRequest::Search, Context->GetRequestStartTime().MicroSeconds()));
    TSimpleSharedPtr<TSuperMind::TRequestContext> smRequestContext(GetSuperMind()->Register(smRequest));

    const TCgiParameters& cgi = Context->GetRequestData().CgiParam;
    if (IsTrue(cgi.Get("broadcast_fetch")) && !SEM.GetConfig().GetSearcherConfig().EnableUrlHash) {
        ythrow yexception() << "broadcast_fetch without EnableUrlHash";
    }

    const TInstant start = Now();
    const bool result = TCommonSearchReplier::ProcessFetch();
    const TInstant finish = Now();

    const TDuration full = finish - Context->GetRequestStartTime();
    const TDuration process = finish - start;
    TRTYAccessLog::LogFetch(Context->GetRequestId(), full, process, result);
    auto ctx = NRTYServer::TUnistatRecordContext(CommonRequestType, full.MilliSeconds(), process.MilliSeconds(), result);
    ctx.ReportByteSize = Stat.ReportByteSize;
    ctx.TotalDocsCount = Stat.TotalDocsCount;
    ctx.ReportDocsCount = Stat.ReportDocsCount;
    TSaasRTYServerSignals::DoUnistatRecordSearch(ctx);
    return result;
}

const TSearchHandlers* TRTYCommonSearchReplier::GetSearchHandlers() const {
    return SEM.GetMetaSearchHandlers();
}

THolder<NMemorySearch::TResponseTimeGuard> TRTYCommonSearchReplier::CreateTimeGuard() const {
    TQueriesMetrics* metric = nullptr;
    if (auto metrics = SEM.GetMainSearchServerMetrics()) {
        switch (CommonRequestType) {
        case RT_Fetch:
            metric = &metrics->SnippetsFetchTime;
            break;
        case RT_Factors:
            metric = &metrics->FactorRequestTime;
            break;
        default:
            metric = &metrics->ResponseTime;
            break;
        }
    }

    return metric ? MakeHolder<NMemorySearch::TResponseTimeGuard>(*metric) : nullptr;
}

TRTYCommonSearchReplier::TRTYCommonSearchReplier(IReplyContext::TPtr context, const TCommonSearch* commonSearcher, const TSearchEnginesManager& sem, ui64 defaultKps)
    : TCommonSearchReplier(context, commonSearcher, nullptr)
    , SEM(sem)
    , DefaultKps(defaultKps)
{
    CommonRequestType = CommonSearcher->TCommonSearch::RequestType(Context->GetRequestData().CgiParam);
}
