#include "manager.h"
#include "const.h"
#include "config.h"
#include "intersect_iterator.h"

#include <saas/rtyserver/components/fullarchive/manager.h>
#include <saas/rtyserver/search/processors/abstract/abstract.h>
#include <saas/library/cgi/cgi.h>
#include <saas/library/regexp_to_trigram/regexp_to_trigram.h>

#include <library/cpp/containers/stack_vector/stack_vec.h>

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

TTrigramIndexManager::TTrigramIndexManager(const TRTYServerConfig& config)
    : NRTYServer::IIndexComponentManager(NRTYServer::TrigramComponentName)
{
    Config = config.ComponentsConfig.Get<TTrigramComponentConfig>(ComponentName);
    CHECK_WITH_LOG(Config);
}

void TTrigramIndexManager::ExtractRequestParams(const TCgiParameters& cgi, TVector<TRegexpSeachInfo>& searchInfos) const {
    bool ignoreCase = IsTrue(cgi.Get("regexp_ignore_case")) && Config->IsIgnoreCase(); // если в конфиге не включено IgnoreCase, то и в поиске оно никак не включится

    const TString& kps = cgi.Get("kps");
    TStackVec<ui64, 8> prefixes;
    if (!kps.empty()) {
        StringSplitter(kps).Split(',').ParseInto(&prefixes);
    } else {
        prefixes.reserve(1);
        prefixes.push_back(0);
    }

    TRegexpToTrigrams parser;
    auto range = cgi.Range("regexp");
    for (ui64 prefix : prefixes) {
        for (const auto& val: range) {
            TRegexpSeachInfo searchInfo(val, prefix, ignoreCase);

            for (auto trigram : parser.Parse(searchInfo.SearchInfo.GetUrl())) {
                searchInfo.TrigramSubqueries.emplace_back(trigram, prefix);
            }
            searchInfos.push_back(searchInfo);
        }
    }
}

ERTYSearchResult TTrigramIndexManager::DoSearch(const TRTYSearchRequestContext& context, ICustomReportBuilder& reportBuilder, const IIndexController& controller) const {
    const TCgiParameters& cgi = context.CgiParams();

    if (cgi.Has("haha") || cgi.Has("DF")) {
        reportBuilder.AddErrorMessage("Two step query request is incorrect for this component");
        return SR_NOT_FOUND;
    }

    const TString timeToWait = cgi.Get("sleep");
    ui64 waitingTime = 0;
    if (timeToWait && TryFromString(timeToWait, waitingTime)) {
        DEBUG_LOG << "Sleeping " << waitingTime << " microseconds" << Endl;
        Sleep(TDuration::MicroSeconds(waitingTime));
    }

    TMessagesCollector errors;
    TVector<TDocIdCandidate> docIds;
    if (!SearchImpl(context, controller, docIds, errors)) {
        reportBuilder.AddErrorMessage(errors.GetStringReport());
        return SR_NOT_FOUND;
    }

    if (docIds.empty()) {
        return SR_NOT_FOUND;
    }

    const IFAManager* manager = dynamic_cast<const IFAManager*>(controller.GetManager(FULL_ARCHIVE_COMPONENT_NAME));
    CHECK_WITH_LOG(manager);

    TVector<NMetaProtocol::TDocument> documents;
    const TString groupingAttr = GetGropingAttr(cgi);
    if (groupingAttr) {
        TVector<TString> categs;
        manager->SearchDocuments(docIds, cgi, documents, groupingAttr, &categs);
        CHECK_WITH_LOG(categs.size() == documents.size());
        for (ui32 i = 0; i < documents.size(); ++i) {
            reportBuilder.AddDocumentToGroup(documents[i], groupingAttr, categs[i]);
        }
    } else {
        manager->SearchDocuments(docIds, cgi, documents);
        for (auto&& doc : documents) {
            reportBuilder.AddDocument(doc);
        }
    }

    return documents.size() == 0 ? SR_NOT_FOUND : SR_OK;
}

bool TTrigramIndexManager::SearchImpl(const TRTYSearchRequestContext& context, const IIndexController& controller, TVector<TDocIdCandidate>& docIds, TMessagesCollector& errors) const {
    // This function returns docIdCandidates, which we should verify further, using fullArc or another source of documents' hashes
    TVector<TRegexpSeachInfo> searchInfos;

    ExtractRequestParams(context.CgiParams(), searchInfos);

    TComponentSearcher::TPtr compSearch;
    TAtomicSharedPtr<IDocProcessor> filter;
    if (context.UseFilters()) {
        compSearch = controller.InitSpecialSearch(context, errors);
        if (compSearch)
            filter = compSearch->CreateFilter();
        if (!filter)
            return false;
    }

    if (!searchInfos) {
        return false;
    }
    for (auto& sInfos : searchInfos) {
        TVector<TDocIdCandidate> currentResult;
        TVector<TTrigramIndexManager::TIterator::TPtr> iterators;
        TDocIdCandidate localCandidate(sInfos.SearchInfo);
        for (auto& subSInfo : sInfos.TrigramSubqueries) {
            localCandidate.SetVerified(true);
            auto it = CreateIterator(sInfos.PropName, subSInfo.GetHash(), filter.Get());
            if (it) {
                iterators.push_back(it);
            }
        }

        for (TIntersectIterator iter(iterators); iter.Valid(); iter.Next()) {
            localCandidate.SetDocId(TWordPosition::Doc(iter.GetPosition()));
            currentResult.push_back(localCandidate);
        }
        for (auto&& i : currentResult) {
            if (!controller.IsRemoved(i.GetDocId())) {
                docIds.push_back(i);
            }
        }
    }
    return true;
}

TTrigramIndexManager::TRegexpSeachInfo::TRegexpSeachInfo(const TStringBuf& regexpQuery, ui64 kps, bool ignoreCase)
    : SearchInfo(ignoreCase ? to_lower(ParseRegexpQuery(regexpQuery).first) : ParseRegexpQuery(regexpQuery).first, kps)
    , PropName(ignoreCase ? TString::Join(ParseRegexpQuery(regexpQuery).second, '@') : ParseRegexpQuery(regexpQuery).second)
{
}

std::pair<TString, TString> TTrigramIndexManager::TRegexpSeachInfo::ParseRegexpQuery(const TStringBuf& regexpQuery) {
    TVector<TStringBuf> args = StringSplitter(regexpQuery).Split('~').SkipEmpty();
    if (args.size() != 2 || args[0].empty() || args[1].empty()) {
        return {"", ""}; // некорректный запрос
    }
    bool inverseMatching = (args[0][args[0].size() - 1] == '!');
    const TStringBuf& regexp = args[1];
    const TStringBuf& prop = args[0].substr(0, args[0].size() - 1);
    if (inverseMatching) {
        return {"", ""}; // индекс не хранит список документов в которых нет определенных триграмм
    }
    return {ToString(regexp), ToString(prop)};
}
