#include "server.h"

#include <search/daemons/httpsearch/neh/request.h>

#include <saas/library/report_builder/simple.h>
#include <saas/library/searchserver/delay.h>
#include <saas/library/searchserver/exception.h>
#include <saas/library/searchserver/protocol.h>

#include <saas/rtyserver/common/common_messages.h>
#include <saas/rtyserver/common/message_collect_server_info.h>
#include <saas/rtyserver/search/source/search_replier.h>
#include <saas/rtyserver/search/source/searchers.h>

#include <search/request/data/reqdata.h>

#include <search/memory/search.h>

class TRTYNehClientRequest: public TYsNehClientRequest, public TNehReplyContext {
private:
    const TSearchEnginesManager& SearchEngineManager;
    const ui64 SearcherIdx;
    THolder<NMemorySearch::TResponseTimeGuard> TimeGuard;
    IIndexController::TPtr IC;
    TBlob Buf;
    TInstant RequestCreatedTime;
    ui32 Pass = 0;

public:
    TRTYNehClientRequest(YandexHttpServer* server, const NNeh::IRequestRef& request, const TSearchEnginesManager& sem, ui64 searcherIdx)
        : TYsNehClientRequest(server, request)
        , SearchEngineManager(sem)
        , SearcherIdx(searcherIdx)
        , RequestCreatedTime(Now())
    {
        const auto [baseHost, basePort] = sem.GetBaseHostAndPort();
        RD_.SetHost(baseHost, basePort);
    }

    // IReplyContext
    TInstant GetRequestStartTime() const override {
        return RequestCreatedTime;
    }

    NNeh::IRequest& GetRequest() override {
        return *Request_;
    }

    NNeh::TRequestOut& GetReplyOutput() override {
        return Reply_;
    }

    NSearch::IOutputContext& Output() override {
        return ReplyContext_;
    }

    const TSearchRequestData& GetRequestData() const override {
        return RD_;
    }

    TSearchRequestData& MutableRequestData() override {
        return RequestData();
    }

    TBlob& GetBuf() override {
        return Buf;
    }

    void LogFailedRequest() {
        // it's really need for rtysearch ?
        TString loggedUrl = Server()->ProtoServer_.GetLogFailedRequests() ? TString{RD_.Query()} : "";
        Server()->LoadLog.AddLog("-1\t%ld\t%d\t%s\n", (long)time(nullptr), 777, loggedUrl.c_str());
    }

    // TYsNehClientRequest
    const TSearchHandlers* GetSearchHandlers() const override {
        return SearchEngineManager.GetBaseSearchHandlers();
    }

    void Process(void* ThreadSpecificResource) override {
        THolder<TRTYNehClientRequest> this_(this);
        {
            auto dummyIt = RequestData().CgiParam.find(TStringBuf("dummy_ping"));
            if (dummyIt != RequestData().CgiParam.end() && IsTrue(dummyIt->second)) {
                TRTYSimpleProtoReportBuilder builder(*this);
                builder.Finish(HTTP_OK);
            }
        }
        Pass++;
        try {
            auto p = RequestData().CgiParam.find(TStringBuf("timeout"));
            if (p != RequestData().CgiParam.end() && IsNumber(p->second)) {
                const ui64 elapsed = MicroSeconds() - RequestData().RequestBeginTime();
                const ui64 timeout = FromString<ui64>(p->second);
                if (Y_UNLIKELY(elapsed >= timeout)) {
                    if (auto metrics = SearchEngineManager.GetBaseSearchServerMetrics()) {
                        metrics->TimeoutDrops.Inc();
                    }

                    LogFailedRequest();
                    return;
                }
            }

            if (RequestType == RT_Unknown) { // first pass
                Y_ASSERT(Pass == 1);
                Y_ASSERT(!Searcher);

                TSearchEnginesManager::TGuardedSearchPtr cs = SearchEngineManager.GetCommonSearch(SearcherIdx);
                if (Y_UNLIKELY(!cs || !cs->GetIndexController() || !cs->GetIndexController()->IsSearching())) {
                    LogFailedRequest();
                    return;
                }
                // Searcher is guaranteed to be something real.
                // here use hack with own additional logic first pass processing request
                // (parsing request to cgi&RP + assign searcher)
                ScanQuery();
                if (!RequestData().CgiParam.Has("component")) {
                    if (!Searcher) {
                        Searcher = cs->GetIndexController()->GetCommonSearch();
                        if (Searcher) {
                            Searcher->IncreaseSearch();
                        }
                    }
                }
                if (!Searcher) {
                    IC = cs->GetIndexController();
                    RequestType = IC->RequestType(RequestData().CgiParam);

                    if (Y_UNLIKELY(RequestType == RT_Unknown)) {
                        LogFailedRequest();
                        return;
                    }
                }
            }

            if (RequestType != RT_Unknown) { // second pass
                Y_ASSERT(!Searcher || (Pass == 2));

                auto metrics = SearchEngineManager.GetBaseSearchServerMetrics();
                if (metrics && !dynamic_cast<const NMemorySearch::TMemorySearch*>(Searcher)) {
                    // We do not have a real HTTP server, so this set of metrics no longer applies:
                    // TSearchServerMetrics *serverMetrics = SearchEngineManager.GetBaseSearchServerMetrics();
                    // ...
                    TQueriesMetrics* timeMetric = nullptr;
                    switch (RequestType) {
                    case RT_Fetch:
                        timeMetric = &metrics->SnippetsFetchTime;
                        break;
                    case RT_Factors:
                        timeMetric = &metrics->FactorRequestTime;
                        break;
                    default:
                        timeMetric = &metrics->ResponseTime;
                        break;
                    }
                    if (Y_LIKELY(timeMetric)) {
                        TimeGuard.Reset(new NMemorySearch::TResponseTimeGuard(*timeMetric));
                    }
                }

                TSearchRequestDelay::Process(RequestData(), "rty_base");
            }

            if (Searcher) {
                Y_UNUSED(this_.Release());
                // not null RequestHanlder guaranteed processing request inplace
                TYsNehClientRequest::Process(ThreadSpecificResource); // must be the last statement... destroys this.
            } else {
                TRTYSimpleProtoReportBuilder builder(*this);
                Y_ASSERT(IC);
                Y_ASSERT(IC->IsSearching());
                TRTYSearcher searcher = TRTYSearcher(RequestData(), SearchEngineManager.GetConfig());
                searcher.RegisterControllers(TVector<IIndexController::TPtr>(1, IC));
                searcher.Search(builder);
                builder.Finish(HTTP_OK);
            }
        } catch (const yexception& e) {
            if (this_) {
                MakeErrorPage(this_.Release(), HTTP_INTERNAL_SERVER_ERROR, e.what());
            } else {
                throw;
            }
        }
    }
};

TBaseSearchServer::TBaseSearchServer(TSearchEnginesManager& sem, const THttpServerOptions& config)
    : TSearchNehServer(TOptions(config, InprocNehProtocol))
    , SearchEngineManager(sem)
{
    RegisterGlobalMessageProcessor(this);
}

TBaseSearchServer::~TBaseSearchServer() {
    UnregisterGlobalMessageProcessor(this);
}

TString TBaseSearchServer::Name() const {
    return "NehBaseSearchServer";
}

bool TBaseSearchServer::Process(IMessage* message) {
    TMessageCollectServerInfo* messCollect = dynamic_cast<TMessageCollectServerInfo*>(message);
    if (messCollect) {
        NehServerRequestRate.AddRate(*messCollect->GetBaseSearchRps());
        return true;
    }
    return false;
}

TAutoPtr<IObjectInQueue> TBaseSearchServer::DoCreateClientRequest(ui64 /*id*/, NNeh::IRequestRef req) {
    TStringBuf ll = req->Service();
    TStringBuf path = ll.substr(0, ll.find_first_of('?'));
    if (Y_UNLIKELY(!path)) {
        ythrow yexception() << "path is empty in basesearch request";
    }

    return new TRTYNehClientRequest(&YServer, req, SearchEngineManager, FromString<ui64>(path));
}

TMetaSearchNehServer::TMetaSearchNehServer(TSearchEnginesManager& sem, const NRTYServer::TSearcherConfig& config,
                                           const THttpServerOptions& serverConfig, const TDefaultKeyPrefix* defaultKeyPrefix)
    : TSearchNehServer(TOptions(serverConfig, MetaSearchNehProtocol))
    , TMetaSearchServerFeatures(sem, config, defaultKeyPrefix)
{
    RegisterGlobalMessageProcessor(this);
}


TMetaSearchNehServer::~TMetaSearchNehServer() {
    UnregisterGlobalMessageProcessor(this);
}

bool TMetaSearchNehServer::Process(IMessage* message) {
    TMessageCollectServerInfo* messCollect = dynamic_cast<TMessageCollectServerInfo*>(message);
    if (messCollect) {
        NehServerRequestRate.AddRate(*messCollect->GetNehSearchRps()); /* TODO */
        return true;
    }
    return false;
}

TString TMetaSearchNehServer::Name() const {
    return "NehMetaSearchServer";
}

TAutoPtr<IObjectInQueue> TMetaSearchNehServer::DoCreateClientRequest(ui64 /*id*/, NNeh::IRequestRef req) {
    return new TMetaSearchNehRequest(&YServer, *this, req);
}
