#include "auth.h"
#include "client.h"
#include "delay.h"
#include "exception.h"
#include "server.h"

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

#include <search/common/profile.h>
#include <search/config/virthost.h>
#include <search/engine/makepage.h>
#include <search/session/comsearch.h>
#include <search/daemons/httpsearch/yshttp.h>

#include <library/cpp/eventlog/eventlog.h>
#include <library/cpp/json/json_writer.h>
#include <library/cpp/http/misc/httpcodes.h>
#include <library/cpp/http/misc/httpdate.h>

#include <util/datetime/base.h>
#include <util/generic/map.h>
#include <util/generic/strbuf.h>
#include <util/generic/string.h>
#include <util/generic/yexception.h>
#include <util/string/cast.h>
#include <library/cpp/cgiparam/cgiparam.h>
#include <util/string/strip.h>
#include <util/string/type.h>
#include <library/cpp/deprecated/atomic/atomic.h>
#include <util/system/defaults.h>
#include <util/system/hostname.h>

TSearchClient::~TSearchClient() {
    try {
        ReleaseConnection();
    } catch (...) {
        DEBUG_LOG << "Connection releasing failed: " << CurrentExceptionMessage() << Endl;
        OnReleaseConnectionFailure();
    }
}

void TSearchClient::AppendPostParamsToQueryStringIfNeed() {
    if (!Buf.Empty()) {
        TStringBuf buf(static_cast<const char*>(Buf.Data()), Buf.Size());
        if (ProcessBodyEncoding(RD.Query(), buf, RD)) {
            Buf.Drop();
        }
    }
}

bool TSearchClient::Reply(void* /*ThreadSpecificResource*/) {
    THolder<NProfile::TMeasurement> measurementHolder;
    measurementHolder.Reset(StartProfile());

    Output().Flush();
    if (!ProcessHeaders()) {
        return true;
    }

    AppendPostParamsToQueryStringIfNeed();

    const auto& serverOptions = HttpServ()->Options();
    RD.Scan();

    const auto& hostName = serverOptions.Host.empty() ? HostName() : serverOptions.Host;
    RD.SetHost(hostName, serverOptions.Port);

    IReplyContext::TPtr context(new THttpReplyContext(this));
    try {
        OnBeginProcess(context);

        if (ProcessSpecialRequest() || !ProcessPreAuth()) {
            return false;
        }

        ISearchReplier::TPtr searchReplier = DoSelectHandler(context);
        CHECK_WITH_LOG(!!searchReplier);
        searchReplier.Release()->Reply();
    } catch (const NSaas::TNotAuthorizedException& e) {
        OnAccessDenied(e.GetInfo(), context);
        MakeErrorPage(context, e.GetHttpCode(), e.what());
    } catch (const TSearchException& e) {
        MakeErrorPage(context, e.GetHttpCode(), e.what());
    } catch (...) {
        MakeErrorPage(context, HTTP_INTERNAL_SERVER_ERROR, CurrentExceptionMessage());
    }
    return false;
}

void TSearchClient::OnReleaseConnectionFailure() {
}

void TSearchClient::OnAccessDenied(const NSaas::TNotAuthorizedInfo&, IReplyContext::TPtr) {
}

void TSearchClient::OnBeginProcess(IReplyContext::TPtr /*context*/) {
}

bool TSearchClient::ProcessPreAuth() {
    return true;
}

void TSearchClient::ProcessAuth() {
}

NProfile::TMeasurement* TSearchClient::StartProfile() {
    NProfile::TMeasurement* result = nullptr;
    if (FirstRun) { // Yes, this is very much a hack, but there's no other reliable way for the
        NProfile::Reset(); // first of the (possibly many) calls to Reply() to start from clean slate.
        result = new NProfile::TMeasurement("requestTotal");
    }
    FirstRun = false; // Save for hacking THttpServer itself, which we're currently avoiding.
    return result;
}
