#include "search_replier.h"
#include "searchers.h"

#include <saas/library/report_builder/simple.h>
#include <saas/rtyserver/unistat_signals/signals.h>
#include <saas/rtyserver/logging/rty_access.h>
#include <saas/rtyserver/config/config.h>
#include <saas/rtyserver/config/searcher_config.h>
#include <saas/rtyserver/search/metrics.h>

#include <search/memory/rtsearchmetrics.h>
#include <search/session/comsearch.h>

void ICustomSearchReplier::OnQueueFailure() {
    NRTYServer::TCommonReplierFeatures::OnQueueFailure(Context, Metrics);
    ISearchReplier::OnQueueFailure();
}

IThreadPool* ICustomSearchReplier::DoSelectHandler() {
    VERIFY_WITH_LOG(!!SearchHandlers, "Not initialized search handlers");
    ERequestType reqType = TCommonSearch::GetRequestType(Context->GetRequestData().CgiParam);
    return GetHandler(reqType);
}

THolder<NMemorySearch::TResponseTimeGuard> ICustomSearchReplier::CreateTimeGuard() const {
    if (Metrics) {
        return MakeHolder<NMemorySearch::TResponseTimeGuard>(Metrics->ResponseTime);
    } else {
        return nullptr;
    }
}

TDuration ICustomSearchReplier::GetDefaultTimeout() const {
    return SearcherConfig.GetScatterTimeout();
}

void ICustomSearchReplier::OnRequestExpired(const int /*httpCode*/) {
    NRTYServer::TCommonReplierFeatures::OnRequestExpired(Context, Metrics);
}

void TEmptySearchReplier::DoSearchAndReply() {
    TRTYSimpleProtoReportBuilder builder(*Context);
    builder.Finish(HTTP_OK);
}

TCustomSearchReplier::TCustomSearchReplier(
    IReplyContext::TPtr context,
    const TSearchHandlers* handlers,
    IIndexController::TPtr customSearcher,
    TSearchServerMetrics* metrics,
    const TRTYServerConfig& config
)
    : ICustomSearchReplierWithConfig(context, handlers, metrics, config)
    , CustomSearcher(customSearcher)
{}

void TCustomSearchReplier::DoSearchAndReply() {
    TRTYSimpleProtoReportBuilder builder(*Context);
    try {
        if (Config.GetSearcherConfig().ExceptionOnSearch) {
            ythrow yexception() << "Configured exception";
        }
        const TInstant start = Now();
        {
            THolder<NMemorySearch::TResponseTimeGuard> timeGuard = CreateTimeGuard();
            TRTYSearcher searcher(Context->GetRequestData(), Config);
            searcher.RegisterControllers({ CustomSearcher });
            searcher.Search(builder);
        }
        const TInstant finish = Now();

        ui32 reportDocsCount = builder.GetDocumentsCount();
        builder.Finish(HTTP_OK);
        ui64 byteSize = builder.GetReportByteSize();
        TRTYAccessLog::LogSearch(Context->GetRequestId(), finish - Context->GetRequestStartTime(), finish - start, reportDocsCount != 0, reportDocsCount, reportDocsCount, byteSize, 0, 0, RT_Search);
    } catch (...) {
        const TString& message = CurrentExceptionMessage();

        TSaasRTYServerSignals::DoUnistatErrorSearch();
        TRTYAccessLog::LogError(Context->GetRequestId(), Now() - Context->GetRequestStartTime(), message);
        WARNING_LOG << "TCustomSearchReplier::SearchAndReply[" << Context->GetRequestId() << "]: " << message << Endl;

        throw;
    }
}

TCustomMetaSearchReplier::TCustomMetaSearchReplier(
    IReplyContext::TPtr context,
    const TSearchHandlers* handlers,
    const TSearchersVector& customSearchers,
    TSearchServerMetrics* metrics,
    const TRTYServerConfig& config
)
    : ICustomSearchReplierWithConfig(context, handlers, metrics, config)
{
    for (auto&& i : customSearchers) {
        Controllers.push_back(i->GetIndexController());
    }
}

TCustomMetaSearchReplier::TCustomMetaSearchReplier(
    IReplyContext::TPtr context,
    const TSearchersVector& customSearchers,
    TSearchServerMetrics* metrics,
    const TRTYServerConfig& config
)
    : ICustomSearchReplierWithConfig(context, nullptr, metrics, config)
{
    for (auto&& i : customSearchers) {
        Controllers.push_back(i->GetIndexController());
    }
}

void TCustomMetaSearchReplier::DoSearchAndReply() {
    TRTYSimpleProtoReportBuilder builder(*Context);
    ui32 code = HTTP_OK;
    try {
        if (Config.GetSearcherConfig().ExceptionOnSearch) {
            ythrow yexception() << "Configured exception";
        }
        const TInstant start = Now();
        {
            THolder<NMemorySearch::TResponseTimeGuard> timeGuard = CreateTimeGuard();
            IRTYSearcher::TPtr searcher = IRTYSearcher::TFactory::Construct(Config.GetSearcherConfig().CustomSearcher, Context->GetRequestData(), Config);
            searcher->RegisterControllers(Controllers);
            CHECK_WITH_LOG(searcher.Get());
            searcher->Search(builder);
        }
        const TInstant finish = Now();

        ui32 reportDocsCount = builder.GetDocumentsCount();
        bool failOnZero = !reportDocsCount && IsTrue(Context->GetCgiParameters().Get("fail_on_zero"));
        if (failOnZero) {
            code = HTTP_INTERNAL_SERVER_ERROR;
        }
        builder.Finish(code);
        ui64 byteSize = builder.GetReportByteSize();
        TRTYAccessLog::LogSearch(Context->GetRequestId(), finish - Context->GetRequestStartTime(), finish - start, reportDocsCount != 0, reportDocsCount, reportDocsCount, byteSize, 0, 0, RT_Search, 0, failOnZero);
    } catch (...) {
        const TString& errorMessage = CurrentExceptionMessage();

        TSaasRTYServerSignals::DoUnistatErrorSearch();
        TRTYAccessLog::LogError(Context->GetRequestId(), Now() - Context->GetRequestStartTime(), errorMessage);
        WARNING_LOG << "TCustomMetaSearchReplier::SearchAndReply[" << Context->GetRequestId() << "]: " << errorMessage << Endl;

        throw;
    }
}

ICustomSearchReplierWithConfig::ICustomSearchReplierWithConfig(
    IReplyContext::TPtr client,
    const TSearchHandlers* handlers,
    TSearchServerMetrics* metrics,
    const TRTYServerConfig& config
)
    : ICustomSearchReplier(client, handlers, metrics, config.GetSearcherConfig())
    , Config(config)
{}
