#include "context.h"

#include <search/meta/scatter/request.h>
#include <search/meta/scatter/helpers.h>
#include <search/meta/scatter/source.h>
#include <search/session/compression/report.h>

#include <saas/searchproxy/configs/serviceconfig.h>
#include <saas/library/searchserver/replier.h>
#include <search/request/data/reqdata.h>

#include <library/cpp/logger/global/global.h>

namespace NProxyMeta {

    TSearchContext::TSearchContext(IReplyContext* context, TAnswerBuilder* answerBuilder, TVector<TString> textOverride, NScatter::ISource& source, CPHashFunction hashFunction, const TServiceConfig& serviceConfig)
        : NScatter::ITask(source)
        , Context(context)
        , HashFunction(hashFunction)
        , Options(source.GetSourceOptions())
        , TextOverride(std::move(textOverride))
        , AnswerBuilder(answerBuilder)
        , ServiceConfig(serviceConfig)
    {
        CHECK_WITH_LOG(Context);
        CHECK_WITH_LOG(AnswerBuilder);
        AnswerBuilder->RegisterRequest(GetSource().Descr);
        const TString& timeout = Context->GetRequestData().CgiParam.Get("timeout");
        ui32 timeoutIntUs;
        if (TryFromString(timeout, timeoutIntUs)) {
            Options.TimeOut = TDuration::MicroSeconds(timeoutIntUs);
        }
    }

    void TSearchContext::DoFinalize() {
        if (!AtomicGet(RepliesCounter)) {
            AnswerBuilder->RegisterFail(GetSource().Descr, ErrorsCollector.GetStringReport());
        }
    }

    void TSearchContext::DoHandleError(const NScatter::TErrorDetails& details) {
        ErrorsCollector.template AddMessage<TLOG_DEBUG>(details.ToString(), Context->GetRequestData().CgiParam.Print());
    }

    NScatter::TParseResult TSearchContext::DoParse(const NScatter::TTaskReply& reply) {
        AtomicIncrement(RepliesCounter);
        if (AtomicGet(SuccessesCounter) == 0 && !AnswerBuilder->RegisterReport(GetSource().Descr, reply, SuccessesCounter)) {
            return NScatter::TParseResult(NScatter::ITask::TemporaryError);
        }
        return NScatter::TParseResult(NScatter::ITask::Finished);
    }

    void TSearchContext::FillRequestData(NScatter::TRequestData& rd) const {
        TCgiParameters cgi = Context->GetRequestData().CgiParam;
        cgi.EraseAll("hr");
        if (cgi.Has("metaservice")) {
            cgi.EraseAll("service");
        }
        if (!TextOverride.empty()) {
            cgi.ReplaceUnescaped("text", TextOverride.cbegin(), TextOverride.cend());
        }
        if (auto proxyMetaConfig = ServiceConfig.GetProxyMetaConfig()) {
            if (cgi.Has("timeout") && proxyMetaConfig->GetSourceTimeoutFactor() < 0.99f) {
                ui32 timeout = proxyMetaConfig->GetSourceTimeoutFactor() * FromString<ui32>(cgi.Get("timeout"));
                cgi.ReplaceUnescaped("timeout", ToString(timeout));
            }
        }
        rd.Url = cgi.Print();
    }

    size_t TSearchContext::CalcHash() const {
        return (*HashFunction)(Context->GetRequestData().CgiParam);
    }

    size_t TSearchContext::GetParallelRequestCount() const {
        CHECK_WITH_LOG(ServiceConfig.GetProxyMetaConfig());
        return ServiceConfig.GetProxyMetaConfig()->GetParallelRequestCount();
    }

    TStringBuf TSearchContext::GetTypeName() const {
        return "request"sv;
    }

    TAnswerBuilder::TAnswerBuilder(IReplyContext& context, const TSelfFlushLogFramePtr eventLogFrame, const bool addEventLogToReport,
            const TRearrangeEngine* rearrangeEnginePtr,
            const THttpStatusManagerConfig& configHttpStatus
        )
        : Context(context)
        , EventLogFrame(eventLogFrame)
        , AddEventLogToReport(addEventLogToReport)
        , RearrangeEnginePtr(rearrangeEnginePtr)
        , ConfigHttpStatus(configHttpStatus)
        , ReportConstructor(context)
    {
    }

    void TAnswerBuilder::RegisterFail(const TString& source, const TString& details) {
        AtomicIncrement(FailedSources);
        TGuard<TAdaptiveLock> guard(Lock);
        ReportConstructor.AddErrorMessage("FROM " + source + ":" + details);
    }

    bool TAnswerBuilder::RegisterReport(const TString& source, const NScatter::TTaskReply& reply, TAtomic& sourceSuccessesCounter) {
        TAtomicSharedPtr<NMetaProtocol::TReport> report(new NMetaProtocol::TReport);
        TString error = "FROM " + (reply.Connection ? (reply.Connection->Host() + ":" + ToString(reply.Connection->Port())) : "unknown host") + ": ";
        if (!!reply.Data && report->ParseFromString(reply.Data)) {
            NMetaProtocol::Decompress(*report);
            const auto& err = report->GetErrorInfo();
            if (err.HasGotError() && err.GetGotError() == ::NMetaProtocol::TErrorInfo_TGotError_YES) {
                SourcesErrors.push_back({err.GetText(), (int)err.GetCode()});
            }
            const NMetaProtocol::TDebugInfo& debugInfo = report->GetDebugInfo();
            if (debugInfo.HasAnswerIsComplete() && !debugInfo.GetAnswerIsComplete()) {
                AtomicIncrement(IncompleteReplies);
            } else {
                if (AtomicIncrement(sourceSuccessesCounter) == 1) {
                    ui32 index = AtomicIncrement(SucceededSources);
                    CHECK_WITH_LOG(Answers.size() > index - 1);
                    Answers[index - 1] = TSourceReply(report, source);
                }
                return true;
            }
        } else {
            AtomicIncrement(IncompleteReplies);
            error += "Incorrect reply";
            TGuard<TAdaptiveLock> guard(Lock);
            ReportConstructor.AddErrorMessage(error + "(" + reply.Data + ")");
        }
        DEBUG_LOG << error << Endl;
        return false;
    }

    void TAnswerBuilder::RegisterRequest(const TString& /*source*/) {
        SourcesCount += 1;
        Answers.resize(SourcesCount);
    }

    void TAnswerBuilder::Answer(const TString& proxyType) {
        const ui64 succeededSources = AtomicGet(SucceededSources);
        const ui64 failedSources = AtomicGet(FailedSources);

        for (const auto& err : SourcesErrors) {
            ReportConstructor.AddReportProperty("SearchErrors.debug", err.Text);
        }

        if (succeededSources != SourcesCount) {
            TStringStream ss;
            ss << "success=" << succeededSources << ";all=" << SourcesCount << ";failed=" << failedSources << Endl;
            TReplyGuard replier(ReportConstructor, AnsweredHttpCode, EventLogFrame, AddEventLogToReport);
            ReportConstructor.AddErrorMessage(ss.Str());
            replier.SetReplyCode((HttpCodes)ConfigHttpStatus.IncompleteStatus);
            return;
        }

        if (RearrangeEnginePtr)
            RearrangeEnginePtr->ProcessReplies(Answers, ReportConstructor, Context);

        TReplyGuard replier(ReportConstructor, AnsweredHttpCode, EventLogFrame, AddEventLogToReport);
        if (proxyType == "proxy") {
            if (Answers.size() == 1) {
                NMetaProtocol::TReport& report = *Answers.front().Report;
                if (report.TotalDocCountSize() == 0) {
                    replier.SetReplyCode((HttpCodes)ConfigHttpStatus.UnknownErrorStatus);
                    ReportConstructor.AddErrorMessage("Incorrect reply - no totaldoccount");
                    return;
                } else {
                    DocumentsCount = report.GetTotalDocCount(0);
                    replier.SetProxyReport(std::move(report));
                }
            } else {
                ReportConstructor.AddErrorMessage("Incompatible configuration - multi keys and non-multi_proxy report");
                replier.SetReplyCode((HttpCodes)ConfigHttpStatus.SyntaxErrorStatus);
                return;
            }
        } else if (proxyType == "multi_proxy") {
            if (EventLogFrame) {
                EventLogFrame->LogEvent(NEvClass::TStartMergeGrouping("all", /*mode=*/0, /*needCount=*/0));
            }
            for (ui32 i = 0; i < succeededSources; ++i) {
                ReportConstructor.ConsumeReport(*Answers[i].Report, Answers[i].Source);
            }
            DocumentsCount = ReportConstructor.GetDocumentsCount();
            if (EventLogFrame) {
                EventLogFrame->LogEvent(NEvClass::TStopMergeGrouping(DocumentsCount));
            }
        } else {
            ReportConstructor.AddErrorMessage("Incorrect proxy type " + proxyType + " on request processing");
            replier.SetReplyCode((HttpCodes)ConfigHttpStatus.UnknownErrorStatus);
            return;
        }

        if (!DocumentsCount) {
            replier.SetReplyCode((HttpCodes)ConfigHttpStatus.EmptySetStatus);
        } else {
            replier.SetReplyCode(HTTP_OK);
        }
    }

    void TAnswerBuilder::AnswerFail(const TString& message) {
        TReplyGuard replier(ReportConstructor, AnsweredHttpCode, EventLogFrame, AddEventLogToReport);
        replier.SetReplyCode(HTTP_INTERNAL_SERVER_ERROR);
        ReportConstructor.AddErrorMessage(message + "/" + Context.GetRequestData().CgiParam.Print());
    }
}
