#pragma once

#include <balancer/kernel/module/conn_descr.h>
#include <balancer/kernel/http2/server/common/http2_common.h>
#include <library/cpp/http/io/headers.h>

#include <balancer/server/server_options.cfgproto.pb.h>
#include <balancer/server/maintask_options.cfgproto.pb.h>

#include <util/system/datetime.h>

#include "transport.h"

namespace NSrvKernel {
    class IModule;

    template<class I>
    class TNodeFactory;

    namespace NProcessCore {
        class TMainTask;
    }
}

namespace NBalancerServer {
    struct TOptions
        : public TMainTaskOptions
        , public TServerOptions
    {
        TOptions& SetNetworkThreads(size_t v) {
            NetworkThreads = v;
            return *this;
        }

        TOptions& SetThreads(size_t v) {
            Threads = v;
            return *this;
        }

        TOptions& SetQueueSize(size_t v) {
            QueueSize = v;
            return *this;
        }

        TOptions& SetPort(ui32 v) {
            Port = v;
            return *this;
        }
    };


    struct THttpRequestEnv {
        explicit THttpRequestEnv(const NSrvKernel::TConnDescr& connDescr);

        //will create or return already created reply transport
        virtual IHttpReplyTransport::TSendingRef GetReplyTransport() = 0;

        const NSrvKernel::THeaders& Headers() const {
            return Request_.Headers();
        }

        bool TrailersAllowed() const {
            auto headers = Headers().GetValuesRef(TStringBuf{NSrvKernel::NHTTP2::REQ_HEADER_TE});
            return std::find(headers.begin(), headers.end(), TStringBuf{NSrvKernel::NHTTP2::REQ_HEADER_TE_TRAILERS}) != headers.end();
        }

        bool IsHttp2() const {
            return IsHTTP2(&Request_);
        }

        const TString& Body() const {
            return Body_;
        }

        bool IsBodyDecompressed() const {
            return BodyDecompressed_;
        }

        NSrvKernel::EMethod Method() const {
            return Request_.RequestLine().Method;
        }

        TStringBuf Path() const {
            return Request_.RequestLine().Path.AsStringBuf();
        }

        TStringBuf Cgi() const {
            auto cgi = Request_.RequestLine().CGI.AsStringBuf();
            if (!cgi.empty() && cgi[0] == '?') {
                cgi = cgi.substr(1);
            }
            return cgi;
        }

        TString FirstLine() const {
            return Request_.FirstLine();
        }

        TInstant StartTime() const {
            return StartTime_;
        }

        TString RemoteAddress() const {
            return RemoteAddress_;
        }

        TCont* GetCont() const {
            return Cont_;
        }

        virtual IInputStream* Input() const { return nullptr; }

        TDuration TransferAndDecodingDuration = TDuration::Zero();

    protected:
        TString Body_;
        bool BodyDecompressed_ = false;
    private:
        NSrvKernel::TRequest Request_;

        const TInstant StartTime_;
        const TString RemoteAddress_;

        TCont* const Cont_;
    };

    using TServerCallback = std::function<NSrvKernel::TError(THttpRequestEnv&)>;
    using TInitializationExtension = std::function<void(TAtomicSharedPtr<NSrvKernel::NProcessCore::TMainTask>)>;

    class TStandaloneServer {
    public:
        TStandaloneServer(const TServerCallback& callback, const TOptions& options,
                          const TInitializationExtension& initExtension = {});
        ~TStandaloneServer();

        void Run(NThreading::TPromise<void>* startPromise = nullptr);
        void Shutdown();
        void Stop();
    private:
        class TImpl;
        THolder<TImpl> Impl_;
    };

    class TServer {
    public:
        TServer(const TServerCallback& callback, TAtomicSharedPtr<NSrvKernel::NProcessCore::TMainTask> mainTask,
                const TServerOptions& options);
        ~TServer();

        void Run();
        void Wait();
        void Stop();
    private:
        class TImpl;
        THolder<TImpl> Impl_;
    };
}
