#include "rty_external_cgi.h"

#include <kernel/searchlog/errorlog.h>
#include <kernel/qtree/richrequest/richnode.h>
#include <saas/rtyserver/search/external_search/rty_index_data.h>
#include <saas/rtyserver/components/fastarchive/manager.h>

#include <library/cpp/string_utils/quote/quote.h>

namespace {
    void ParseFactorsIndices(const TStringBuf value, TVector<ui32>& result) {
        for (const TStringBuf& part: StringSplitter(value).Split(',').SkipEmpty()) {
            TStringBuf left, right;
            part.Split('-', left, right);
            if (right.empty()) {
                result.push_back(FromString<ui32>(left));
            } else {
                for (ui32 idx = FromString<ui32>(left); idx <= FromString<ui32>(right); idx++) {
                    result.push_back(idx);
                }
            }
        }
    }
} // namespace

TRTYCgiReader::TRTYCgiReader(const IArchiveData* ad, const IRemapperUrlDocId& remapper, const TRTYIndexData& indexData)
    : IndexData(indexData)
    , AD(ad)
    , FastArchive(IndexData.GetFastArchiveManager())
    , RtyDocProcessor(nullptr)
    , Remapper(remapper)
    , Factors(IndexData.GetFactorsConfig())
    , RankModel(nullptr)
    , FastRankModel(nullptr)
    , FilterModel(nullptr)
    , FastFilterModel(nullptr)
    , DocsAttrs(IndexData.GetDocsAttrs())
    , RP(nullptr)
    , FbMode(EFilterBorderMode::Default)
    , FastFbMode(EFilterBorderMode::Default)
    , FilterBorder(0)
    , FastFilterBorder(0)
    , BorderKeepRefineDoc(false)
    , AllFactors(false)
    , AllFactorsExt(false)
    , AllStaticFactors(false)
    , SimulateNotJustFastRankForRTY(false)
    , UseExternalAnnCalcer(false)
    , DisableQuorumAnn(false)
    , EnableSoftnessInTextQuorum(false)
    , RequestRtyFactors(false)
{}

void TRTYCgiReader::TreatCgiParamRelevSet(TStringBuf value, TMap<ui32, ui32>& userSrcToDst) {
    TString tmp;
    for (const auto& i: StringSplitter(value).Split(',').SkipEmpty()) {
        const TStringBuf token = i.Token();
        const size_t pos = token.find(':');
        if (pos == TStringBuf::npos || pos == value.size() - 1)
            continue;
        tmp.assign(token.data(), pos);
        const size_t dst = Factors->GetFactorGlobalNum(tmp);
        tmp.assign(token.data() + pos + 1, token.length() - pos - 1);
        const size_t src = Factors->GetFactorGlobalNum(tmp);

        if (Y_UNLIKELY(dst == NRTYFactors::NOT_FACTOR || src == NRTYFactors::NOT_FACTOR)) {
            Y_ENSURE(PoliteOpts.AllowMissingUserFactors(), "incorrect factor name in cgi: " << token);
            SEARCH_DEBUG << "incorrect factor name in cgi: " << token << Endl;
            continue;
        }
        userSrcToDst[src] = dst;
    }
}

void TRTYCgiReader::TreatCgiParams(TRequestParams& rp, const TCgiParameters& cgiParams) {
    QSReq = cgiParams.Get("qs_req");

    TreatCgiParamsIRelev(rp, cgiParams);
    TreatCgiParamKps(cgiParams);
    rp.KeyPrefixes = Kps;

    SearchContext.Reset(new TRTYSearchRequestContext(cgiParams, rp, IndexData.GetRTYSearcherConfig()->Owner));
    if (SearchContext->UseFilters()) {
        TMessagesCollector errors;
        TAtomicSharedPtr<IDocProcessor> proc;
        ComponentSearcher = IndexData.GetIndexController()->InitSpecialSearch(*SearchContext, errors);
        if (ComponentSearcher)
            proc = ComponentSearcher->CreateFilter();
        if (proc) {
            DocProcessors.Add(proc);
        }
    }

    if (AD) {
        RtyDocProcessor = TRTYDocProcessor::Create(rp, Remapper, *AD, FastArchive, cgiParams);
        if (RtyDocProcessor) {
            DocProcessors.Add(RtyDocProcessor);
        }
    }

    if (!DocProcessors.Empty()) {
        rp.DocProcessor = &DocProcessors;
    }

    RP = &rp;
}

void TRTYCgiReader::AddExtraFactorsByNameFromCgi(const TCgiParameters& cgiParams, TStringBuf source) {
    for (const auto& factorName: cgiParams.Range(source)) {
        const NRTYFactors::TFactor* factor = Factors->GetFactorByName(factorName);
        if (factor == nullptr) {
            Y_ENSURE(PoliteOpts.AllowMissingExtraFactors(), "incorrect factor name in cgi: " << factorName);
            continue;
        }

        const ui32 factorIdx = static_cast<ui32>(factor->IndexGlobal);
        ExtraFactors.push_back(factorIdx);

        // we have an issue that under some conditions we cannot if have JustFastRank
        // that's why we calculate only fast factors: user & static
        switch (factor->FactorType) {
            case NRTYFactors::TFactorType::ftUser:
            case NRTYFactors::TFactorType::ftStatic:
                ExtraFactorsFastRank.push_back(factorIdx);
                break;
            default:
                break;
        }
    }
}

void TRTYCgiReader::TreatCgiParamsIRelev(TRequestParams& rp, const TCgiParameters& cgiParams) {
    TString formula;
    TString fastFormula;
    TString filterFormula;
    TString fastFilterFormula;
    TString baseFormula;
    TString polynom;
    TString fastPolynom;
    TString filterPolynom;
    TString fastFilterPolynom;
    TString namedFactorSet;
    TMap<ui32, ui32> userSrcToDst;
    TVector<TString> calcUserDirectives, storeUserDirectives;
    TVector<ui32> extraFactorsIndicies;
    bool invalidateCaches = false;
    for (const auto& relev: cgiParams.Range("relev")) {
        TreatCgiParamRelev(relev, userSrcToDst, calcUserDirectives, storeUserDirectives, formula, fastFormula, filterFormula, fastFilterFormula, namedFactorSet, baseFormula, polynom, fastPolynom, filterPolynom, fastFilterPolynom, extraFactorsIndicies, invalidateCaches);
    }

    ExtraFactors.reserve(ExtraFactors.size() + cgiParams.NumOfValues("fsgta") + cgiParams.NumOfValues("gta") + extraFactorsIndicies.size());
    AddExtraFactorsByNameFromCgi(cgiParams, "fsgta");
    if (!rp.IsJustFastRank()) {
        AddExtraFactorsByNameFromCgi(cgiParams, "gta");
    }

    const auto fsgtaFactors = cgiParams.Range("fsgta");
    RequestRtyFactors = std::any_of(fsgtaFactors.begin(), fsgtaFactors.end(), [&](const auto& item)->bool { return item == "_RtyFactors"; });

    // allow factors indicies to be missing without check on PoliteOpts.AllowMissingExtraFactors()
    // this is an internal option for searchproxy to tell what factors does it need and due to possible configs inconsistence
    // some factors in rtyserver might be missing in this case we should calc what we have and report an error in signal (TODO)
    for (const ui32 factorIndex: extraFactorsIndicies) {
        const NRTYFactors::TFactor* factor = Factors->GetFactorByFormulaIndex(factorIndex, false);
        if (factor == nullptr) {
            continue;
        }
        ExtraFactors.push_back(factorIndex);
    }

    if (namedFactorSet) {
        const bool defined = Factors->GetFactorSet(namedFactorSet);
        Y_ENSURE(defined || PoliteOpts.AllowMissingExtraFactors(), "incorrect factor_set name in cgi: " << namedFactorSet);
        if (defined)
            NamedFactorSet = namedFactorSet;
    }

    RankModel = Factors->GetRankModel(formula, baseFormula, polynom);
    FastRankModel = Factors->GetFastRankModel(fastFormula, fastPolynom);

    FilterModel = Factors->GetFilterModel(filterFormula, filterPolynom);
    if (FilterModel == nullptr && FilterBorder > 0.0f) {
        FilterModel = RankModel;
    }

    FastFilterModel = Factors->GetFastFilterModel(fastFilterFormula, fastFilterPolynom);
    if (FastFilterModel == nullptr && FastFilterBorder > 0.0f) {
        FastFilterModel = FastRankModel;
    }

    const NRTYFeatures::TImportedFunctions* imports = IndexData.GetImportedFunctions();
    UserFactorCalcer.Reset(NRTYFactors::TUserFactorsCalcer::Create(calcUserDirectives, storeUserDirectives, PoliteOpts, userSrcToDst, *Factors, DocsAttrs, imports, IndexData.GetDDKManager(), &rp, SearchStatistics, invalidateCaches));
}

void TRTYCgiReader::FillProperties(TSearcherProps* props) const {
    if (!!RtyDocProcessor)
        RtyDocProcessor->FillProperties(props);
}

void TRTYCgiReader::TreatCgiParamKps(const TCgiParameters& cgiParams) {
    for (const auto& it : StringSplitter(cgiParams.Get("rbkps")).Split(',').SkipEmpty()) {
        Kps.push_back(0);
        TryFromString<ui64>(it.Token(), Kps.back());
    }
    if (Y_UNLIKELY(Kps.size() > 1)) {
        SortUnique(Kps);
    }
}

void TRTYCgiReader::TreatCgiParamRelev(
    const TString& value,
    TMap<ui32, ui32>& userSrcToDst,
    TVector<TString>& calcUserDirectives,
    TVector<TString>& storeUserDirectives,
    TString& formula,
    TString& fastFormula,
    TString& filterFormula,
    TString& fastFilterFormula,
    TString& factorSet,
    TString& baseRankModel,
    TString& polynom,
    TString& fastPolynom,
    TString& filterPolynom,
    TString& fastFilterPolynom,
    TVector<ui32>& extraFactorsIdx,
    bool& invalidateCaches
) {
    TSet<TString> keysAlwaysPositive({"strict_factors_cgi", "all_static_factors", "simulate_not_just_fast_rank",
        "enable_softness", "ann_saas", "no_ann_saas"});

    for (const auto& i: StringSplitter(value).Split(';').SkipEmpty()) {
        TStringBuf token = i.Token();
        if (token == "all_factors") { //shortcut for the most frequent relev
            AllFactors = true;
            continue;
        }

        TStringBuf key;
        TStringBuf value;
        bool hasEquals = token.TrySplit('=', key, value);
        if (!hasEquals) {
            key = token;
        }
        if (!hasEquals || keysAlwaysPositive.contains(key)) {
            if (key == "strict_factors_cgi") {
                PoliteOpts = NRTYFactors::TFactorPoliteness::Strict();
            } else if (key == "all_static_factors") {
                AllStaticFactors = true;
            } else if (key == "simulate_not_just_fast_rank") {
                SimulateNotJustFastRankForRTY = true;
            } else if (key == "enable_softness") {
                EnableSoftnessInTextQuorum = true;
            } else if (key == "ann_saas") {
                UseExternalAnnCalcer = true;
            } else if (key == "no_ann_saas") {
                UseExternalAnnCalcer = false;
                DisableQuorumAnn = true;
            }
            continue;
        }

        if (key == "all_factors") {
            AllFactors = true;
            if (value == "ext") {
                AllFactorsExt = true;
            }
        } else if (key == "formula")
            formula = value;
        else if (key == "fast_formula")
            fastFormula = value;
        else if (key == "base_rank_model")
            baseRankModel = value;
        else if (key == "polynom")
            polynom = value;
        else if (key == "fast_polynom")
            fastPolynom = value;
        else if (key == "filter_polynom")
            filterPolynom = value;
        else if (key == "fast_filter_polynom")
            fastFilterPolynom = value;
        else if (key == "set")
            TreatCgiParamRelevSet(value, userSrcToDst);
        else if (key == "calc")
            calcUserDirectives.emplace_back(value);
        else if (key == "store")
            storeUserDirectives.emplace_back(value);
        else if (key == "filter")
            filterFormula = value;
        else if (key == "filter_border") {
            FilterBorder = 0;
            TryFromString(value, FilterBorder);
        } else if (key == "fb_mode") {
            FbMode = EFilterBorderMode::Default;
            TreatCgiParamFbMode(value, FbMode);
        } else if (key == "fast_filter")
            fastFilterFormula = value;
        else if (key == "fast_filter_border") {
            FastFilterBorder = 0;
            TryFromString(value, FastFilterBorder);
        } else if (key == "fast_fb_mode") {
            FastFbMode = EFilterBorderMode::Default;
            TreatCgiParamFbMode(value, FastFbMode);
        } else if (key == "factors") {
            factorSet = value;
        } else if (key == "border_keep_refine") {
            BorderKeepRefineDoc = IsTrue(value);
        } else if (key == "tm") {
            TmName = value;
        } else if (key == "imitate_qr_quorum") {
            ImitateQrQuorum = IsTrue(value);
        } else if (key == "factors_indices") {
            ParseFactorsIndices(value, extraFactorsIdx);
        } else if (key == "factorann_hits_limit") {
            FactorAnnHitsLimit = FromStringWithDefault(value, 0);
        } else if (key == "invalidate_caches") {
            invalidateCaches = IsTrue(value);
        }
    }
}

void TRTYCgiReader::TreatCgiParamFbMode(TStringBuf value, EFilterBorderMode& mode) {
    if (value == "default") {
        mode = EFilterBorderMode::Default;
    } else if (value == "mxonly") {
        mode = EFilterBorderMode::MatrixNetOnly;
    }
}

void TRTYCgiReader::FillDynamicContext(TRTYDynamicFeatureContext& ctx) const {
    ctx.RP = RP;
    ctx.AD = AD;
    ctx.TD = &IndexData.GetIndexTextOpts();
    ctx.Makeup = IndexData.GetMakeupReader();
}

bool TRTYCgiReader::IsQuorumAnnDisabled(bool rtyAnnSaas) const {
    // When the SaaS implementation of Ann/Factorann calcers is used (rtyAnnSaas == true),
    // we ignore &pron=no_factorann (RP->CalcFactorAnn == false) for quorum annotations
    return DisableQuorumAnn || (!rtyAnnSaas && RP && !RP->CalcFactorAnn);
}

const NRTYFactors::TUserFactorsCalcer* TRTYCgiReader::GetUserFactorsCalcer() const {
    return UserFactorCalcer.Get();
}
