#pragma once

#include "replier.h"

#include <saas/library/daemon_base/daemon/base_http_client.h>
#include <search/common/interfaces.h>
#include <search/engine/makepage.h>
#include <search/engine/search_output.h>
#include <search/output_context/stream_output_context.h>
#include <search/request/data/reqdata.h>

#include <kernel/reqerror/reqerror.h>

#include <library/cpp/http/misc/httpcodes.h>

class TCommonSearch;

namespace NProfile {
    class TMeasurement;
}

class TSearchClient: public TCommonHttpClient<TSearchRequestData> {
public:
    virtual ~TSearchClient();

    bool Reply(void* ThreadSpecificResource) override;
    virtual void OnBeginProcess(IReplyContext::TPtr context);
    virtual void OnReleaseConnectionFailure();
    virtual void OnAccessDenied(const NSaas::TNotAuthorizedInfo&, IReplyContext::TPtr context);
    virtual bool ProcessPreAuth();
    virtual void ProcessAuth();

    TInstant GetRequestStartTime() const {
        return RequestStartTime;
    }

    TBlob& GetBuf() {
        return Buf;
    }

    TSearchRequestData& MutableRequestData() {
        return RD;
    }

private:
    virtual ISearchReplier::TPtr DoSelectHandler(IReplyContext::TPtr context) = 0;
    // Lifted verbatim from search/daemons/httpsearch/yshttp.cpp(YSClientRequest::StartProfile())
    NProfile::TMeasurement* StartProfile();
    void AppendPostParamsToQueryStringIfNeed();

private:
    TInstant RequestStartTime = Now();
    bool FirstRun = true;
};

class THttpReplyContext: public IReplyContext {
private:
    THolder<TSearchClient> Client;
    TSearchOutputStack OutContext;
    TMap<TString, TString> HttpHeaders;
    bool ConnectionReleased = false;
public:
    THttpReplyContext(TSearchClient* client)
        : Client(client)
        , OutContext(&(Client->Output()))
    {
        HttpHeaders.insert(std::make_pair("Content-Type", "text/plain"));
    }

    void MakeAuth() override {
        Client->ProcessAuth();
    }

    TSearchOutputStack& Output() override {
        CHECK_WITH_LOG(!ConnectionReleased);
        return OutContext;
    }

    TSearchRequestData& MutableRequestData() override {
        return Client->MutableRequestData();
    }

    TInstant GetRequestStartTime() const override {
        return Client->GetRequestStartTime();
    }

    const TSearchRequestData& GetRequestData() const override {
        return Client->GetRequestData();
    }

    TBlob& GetBuf() override {
        return Client->GetBuf();
    }

    const TRequestParams& GetRP() const override {
        return *Client->GetRequestData().RP;
    }

    long GetRequestedPage() const override {
        return 0;
    }

    bool IsHttp() const override {
        return true;
    }

    void MakeSimpleReply(const TBuffer& buf, int code = HTTP_OK) override {
        MakeSimpleReplyImpl(buf, code);
    }

    template <class T>
    void MakeSimpleReplyImpl(const T& buf, int code = HTTP_OK) noexcept {
        MakeSimpleReplyImpl<T>(Output(), buf, code, HttpHeaders);
        ReleaseConnection();
    }

    void ReleaseConnection() noexcept try {
        ConnectionReleased = true;
        Client->ReleaseConnection();
    } catch (...) {
        ERROR_LOG << "Exception during ReleaseConnection: " << CurrentExceptionMessage() << Endl;
    }

    template <class T>
    static void MakeSimpleReplyImpl(IOutputStream& output, const T& buf, int code = HTTP_OK, const TMap<TString, TString>& headers = TMap<TString, TString>()) noexcept try {
        TSearchOutputStack ctx(&output);
        MakeSimpleReplyImpl(ctx, buf, code, headers);
    } catch (...) {
        ERROR_LOG << "Exception during MakeSimpleReply: " << CurrentExceptionMessage() << Endl;
    }

    template <class T>
    static void MakeSimpleReplyImpl(NSearch::IOutputContext& output, const T& buf, int code = HTTP_OK, const TMap<TString, TString>& headers = TMap<TString, TString>()) noexcept try {
        auto CrLf = "\r\n";
        output.Write(TStringBuf("HTTP/1.1 "));
        output.Write(HttpCodeStrEx(code));
        output.Write(CrLf);
        for (auto&& header : headers) {
            output.Write(header.first);
            output.Write(": ");
            output.Write(header.second);
            output.Write(CrLf);
        }

        output.Write("Content-Length: ");
        output.Write(ToString(buf.size()));
        output.Write(CrLf);
        output.Write(CrLf);
        output.Write(TStringBuf(buf.data(), buf.size()));
        output.Flush();
    } catch (...) {
        ERROR_LOG << "Exception during MakeSimpleReply: " << CurrentExceptionMessage() << Endl;
    }

    void AddReplyInfo(const TString& key, const TString& value) override {
        HttpHeaders[key] = value;
    }

    void Print(const TStringBuf& data) override {
        Output().Write(data);
    }
};
