#pragma once

#include "access_log_out.h"
#include "conn_stats.h"
#include "ssl_props.h"

#include <balancer/kernel/client_hints/client_hints_face.h>
#include <balancer/kernel/cookie/gdpr/gdpr.h>
#include <balancer/kernel/cpu/cpu_limiter.h>
#include <balancer/kernel/ctl/ctl.h>
#include <balancer/kernel/http/parser/client_proto.h>
#include <balancer/kernel/net/address.h>
#include <balancer/kernel/net/socket.h>
#include <balancer/kernel/srcrwr/srcrwr_addrs.h>

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

#include <util/generic/maybe.h>
#include <util/generic/xrange.h>
#include <util/stream/null.h>

#include <array>

namespace NSrvKernel {
    class TFromClientDecoder;
    class TRequest;
    class TResponse;
    class IHttpOutput;
    class TAttemptsRateLimiter;

    struct TUATraits;

    using TRequestHash = ui64;

    struct TRequestWithBody {
        THolder<TRequest> Request;
        TString Body;
    };

    struct THostInfo {
        TString Host;
        TString CachedIp;
        TIpPort Port;
        bool IsSrcRwr = false;
    };

    class IAttemptsHolder {
    public:
        virtual ~IAttemptsHolder() = default;
        virtual size_t AttemptsMax() const = 0;
        virtual size_t AttemptsAvailable() const = 0;
        virtual size_t FastAttemptsAvailable() const = 0;
        virtual void ResetAttempts() = 0;
        virtual void ResetDcAttempts() = 0;
        virtual ui64 RegisterAttempt() = 0;
        virtual void UnregisterAttempt() = 0;
        virtual bool RegisterFail(const TError& /*error*/, bool /*fast503*/, bool /*hedgedRequest*/, ui64 /*id*/) = 0;

        virtual void SetEndpoint(const TString& /*endpoint*/, bool /*hedgedRequest*/) {}
        virtual void SetEndpoint(const TString& /*host*/, int /*port*/, bool /*hedgedRequest*/) {}
        virtual TString GetEndpoint() const { return {}; }

        virtual NSrcrwr::TAddrs* SrcrwrAddrs() { return nullptr; }

        virtual void NotifySourceAttempt(const TString& /*addr*/, const TString& /*location*/, ui64 /*id*/) const {}
        virtual void NotifySourceAttemptFinished(ui64 /*id*/, bool /*error*/) {}

        virtual TInstant GetHedgedSoftDeadline() { return TInstant::Max(); }
        virtual void NotifyAttempt(ui64 id, bool hedged) {
            Y_UNUSED(id);
            Y_UNUSED(hedged);
        };
        virtual void NotifyHedgedRequestSuccess() const {};
        virtual void NotifyRpsLimiterError() const {};
        virtual bool ShouldSendHedged() const { return true; }
        virtual void NotifyHedgedRequestUnallowed() const {}
        virtual bool IsHedgedAttemptAllowed() const { return true; }

        virtual IHttpOutput* HttpOutput(std::function<void()> onSuccessCallback, IHttpOutput* slave, bool hedged) {
            Y_UNUSED(onSuccessCallback);
            Y_UNUSED(slave);
            Y_UNUSED(hedged);
            return nullptr;
        }
        virtual void CallOnSuccessCallback(size_t /*index*/) {};

        virtual bool ShouldValidateBody() const { return false; }

        virtual TRequestWithBody GetFirstRequest();
        virtual TRequestWithBody GetRetryRequest();
        virtual TRequestWithBody GetHedgedRequest();

        virtual void ModifyRequest(TRequest& /*request*/, const THostInfo& /*hostInfo*/) {}

        virtual bool UseEncryption() const = 0;

        virtual TMaybe<ui64> Hash() const { return Nothing(); }

        virtual TDuration GetConnectTimeout() const { return TDuration::Zero(); }

        virtual void ResetStartTime(bool /*hedgedRequest*/) const { return; }
        virtual void ReadyForHedged() = 0;
        virtual void CancelHedged() = 0;
        virtual bool WaitReadyForHedged(TCont* cont) = 0;

        void Timeout() {
            TimedOut_ = true;
        }

        bool IsTimedOut() const {
            return TimedOut_;
        }

        virtual TAttemptsRateLimiter* GetRateLimit() const noexcept { return nullptr; }
        virtual void OnReask(bool hedgedRequest, bool allowed) {
            Y_UNUSED(hedgedRequest);
            Y_UNUSED(allowed);
        }

        virtual bool RetryAllowed() const noexcept { return true; }
      private:
        bool TimedOut_ = false;
    };

    class TTcpConnProps : TNonCopyable {
    public:
        IWorkerCtl* Process;
        const TAddrHolder* RemoteAddress = nullptr;
        const TAddrHolder* LocalAddress = nullptr;
        const TSocketHolder* Socket = nullptr;
        TSslProps Ssl;
        THashMap<ui64, i64> Experiments;
        TMaybe<TUATraits> UATraits;
        TCpuLimiter* CpuLimiter = nullptr;
        NGdprCookie::TGdprCache GdprCache;
        bool TcpRstOnError = true;
        bool SkipKeepalive = false;
        TMaybe<TString> P0f;

    public:
        explicit TTcpConnProps(
                IWorkerCtl* process,
                const TAddrHolder& remoteAddress,
                const TAddrHolder& localAddress,
                TSocketHolder* socket
        ) noexcept
                : Process(process)
                , RemoteAddress(&remoteAddress)
                , LocalAddress(&localAddress)
                , Socket(socket)
        {}

        explicit TTcpConnProps(
            IWorkerCtl& process,
            const TAddrHolder& remoteAddress,
            const TAddrHolder& localAddress,
            TSocketHolder* socket
        ) noexcept
            : TTcpConnProps(&process, remoteAddress, localAddress, socket)
        {}
    };


    class TConnProps {
        TConnProps(const TConnProps&) = default;
        TConnProps& operator=(const TConnProps&) = delete;

    public:
        TTcpConnProps& Parent;

        TInstant Start; // For X-Req-Id generator
        ui64 Random = 0; // For X-Req-Id generator
        TSocketIo* SocketIo = nullptr;
        TConnStats ConnStats;

        NGdprCookie::TGdpr Gdpr;

        NSrcrwr::TAddrs* SrcrwrAddrs = nullptr;
        bool UserConnIsSsl = false;

    public:
        explicit TConnProps(
            TTcpConnProps& parent,
            TInstant start,
            ui64 random,
            TSocketIo* socketIo = nullptr
        ) noexcept
            : Parent(parent)
            , Start(start)
            , Random(random)
            , SocketIo(socketIo)
        {}

        TConnProps Copy() const noexcept {
            return *this;
        }
    };

    class IClientRequest;

    enum class ERequestType {
        First,
        Retry,
        Hedged
    };

    class TConnDescr {
        TConnDescr(const TConnDescr&) = default;
        TConnDescr& operator=(const TConnDescr&) = default;

    public:
        IIoInput* Input = nullptr;
        IHttpOutput* Output = nullptr;
        TConnProps* Properties = nullptr;

        TRequest* Request = nullptr;

        TAccessLogOutput ExtraAccessLog = nullptr;
        TRequestHash Hash = 0;
        TLog* ErrorLog = nullptr;
        TFromClientDecoder* HttpDecoder = nullptr;
        IAttemptsHolder* AttemptsHolder = nullptr;
        IClientRequest* ClientRequest = nullptr;
        ui64 Id = 0;
        ERequestType RequestType = ERequestType::First;
        bool HaveFullBody = false;

    public:
        TConnDescr(
            IIoInput& input,
            IHttpOutput& output,
            TConnProps& properties,
            IOutputStream* accessLogSlave = nullptr
        ) noexcept
            : Input(&input)
            , Output(&output)
            , Properties(&properties)
            , ExtraAccessLog(accessLogSlave ? accessLogSlave : &Cnull)
        {}

        TConnDescr(TConnDescr&&) = default;
        TConnDescr& operator=(TConnDescr&&) = default;

        TConnDescr Copy() const noexcept {
            return *this;
        }

        TConnDescr Copy(TConnProps& props) const noexcept {
            TConnDescr copy = *this;
            copy.Properties = &props;
            return copy;
        }

        TConnDescr CopyIn(IIoInput& input) const noexcept {
            TConnDescr copy = *this;
            copy.Input = &input;
            return copy;
        }

        TConnDescr CopyOut(IHttpOutput& output) const noexcept {
            TConnDescr copy = *this;
            copy.Output = &output;
            return copy;
        }

        TConnDescr Copy(TRequest* req) const noexcept {
            TConnDescr copy = *this;
            copy.Request = req;
            return copy;
        }

        TConnDescr Copy(TRequestHash hash) const noexcept {
            TConnDescr copy = *this;
            copy.Hash = hash;
            return copy;
        }

        TConnDescr Copy(IIoInput& input, IHttpOutput& output) const noexcept {
            TConnDescr copy = *this;
            copy.Input = &input;
            copy.Output = &output;
            return copy;
        }

        TConnDescr Copy(IIoInput& input, IHttpOutput& output, TConnProps& properties) const noexcept {
            TConnDescr copy = *this;
            copy.Input = &input;
            copy.Output = &output;
            copy.Properties = &properties;
            return copy;
        }

        const NAddr::IRemoteAddr& RemoteAddr() const noexcept {
            return *Properties->Parent.RemoteAddress->Addr();
        }

        const NAddr::IRemoteAddr& LocalAddr() const noexcept {
            return *Properties->Parent.LocalAddress->Addr();
        }

        IWorkerCtl& Process() const noexcept {
            return *Properties->Parent.Process;
        }

        bool HasProcess() const noexcept {
            return Properties && Properties->Parent.Process;
        }

        TCpuLimiter* CpuLimiter() const noexcept {
            return Properties->Parent.CpuLimiter;
        }

        TSslProps& Ssl() const noexcept {
            return Properties->Parent.Ssl;
        }

        TString RemoteAddrPortStr() const noexcept {
            return Properties->Parent.RemoteAddress->AddrPortStr();
        }

        TString LocalAddrPortStr() const noexcept {
            return Properties->Parent.LocalAddress->AddrPortStr();
        }

        TString RemoteAddrStr() const noexcept {
            return Properties->Parent.RemoteAddress->AddrStr();
        }

        TString LocalAddrStr() const noexcept {
            return Properties->Parent.LocalAddress->AddrStr();
        }
    };

    static_assert(sizeof(TConnDescr) <= 128, "TConnDescr has become too big");
}
