#pragma once

#include <balancer/kernel/helpers/errors.h>
#include <balancer/kernel/helpers/exceptionless.h>
#include <balancer/kernel/http/parser/common_headers.h>
#include <balancer/kernel/http/parser/http.h>
#include <balancer/kernel/io/iobase.h>
#include <balancer/kernel/net/socket.h>
#include <balancer/kernel/ssl/sslio.h>

#include <util/string/strip.h>

namespace NSrvKernel {
class TBackendConfig;
struct THttpsSettings;
}

namespace NConnectionManager {
    class TSocketContainer;
}

namespace NModProxy {
    using namespace NSrvKernel;

    enum class ETransferType {
        BackendToClient,
        ClientToBackend
    };

    TError WrapIntoBackendError(TError err, bool fastError = false) noexcept;


    class TBackendInput: public IIoInput {
    public:
        TBackendInput(IIoInput* slave) noexcept
            : Slave_(slave)
        {}

    private:
        TError DoRecv(TChunkList& lst, TInstant deadline) noexcept override {
            return WrapIntoBackendError(Slave_->Recv(lst, deadline));
        }

    private:
        IIoInput* const Slave_ = nullptr;
    };

    class TBackendOutput final : public IIoOutput {
    public:
        TBackendOutput(IIoOutput* slave) noexcept
            : Slave_(slave)
        {}

    private:
        TError DoSend(TChunkList lst, TInstant deadline) noexcept override {
            return WrapIntoBackendError(Slave_->Send(std::move(lst), deadline));
        }

    private:
        IIoOutput* const Slave_ = nullptr;
    };


    class TBufferingOutput final : public IHttpOutput {
    public:
        TBufferingOutput(IHttpOutput* slave) noexcept
            : Slave_(slave)
        {}

    private:
        TError DoSendHead(TResponse&& response, const bool forceClose, TInstant) override {
            Response_ = std::move(response);
            ForceClose_ = forceClose;
            return {};
        }

        TError DoSendTrailers(THeaders&& headers, TInstant) override {
            Trailers_ = std::move(headers);
            return {};
        }

        TError DoSend(TChunkList lst, TInstant deadline) noexcept override {
            if (!lst.Empty()) {
                Buffer_.Append(std::move(lst));
                return {};
            } else {
                Y_PROPAGATE_ERROR(Slave_->SendHead(std::move(Response_), ForceClose_, deadline));
                if (!Buffer_.Empty()) {
                    Y_PROPAGATE_ERROR(Slave_->Send(std::move(Buffer_), deadline));
                }
                if (Trailers_.Size()) {
                    Y_PROPAGATE_ERROR(Slave_->SendTrailers(std::move(Trailers_), deadline));
                }
                return Slave_->SendEof(deadline);
            }
        }

    private:
        TResponse Response_;
        THeaders Trailers_;
        bool ForceClose_ = false;
        TChunkList Buffer_;
        IHttpOutput* const Slave_ = nullptr;
    };


    struct TBackendIo {
        TBackendIo(THolder<TSocketHolder> s, TContExecutor* e, EPollMode pollMode) noexcept
            : Sock_(std::move(s))
            , SocketIo_(Sock_.Get(), e, TSocketOut::DefaultMaxInQueue, pollMode)
        {}

        IIoInput& Input() noexcept {
            return SocketIo_.In();
        }

        IIoOutput& Output() noexcept {
            return SocketIo_.Out();
        }

        TSocketIo& SocketIo() noexcept {
            return SocketIo_;
        }

        TSocketHolder& Sock() noexcept {
            return *Sock_;
        }

        THolder<TSocketHolder>& SockHolder() noexcept {
            return Sock_;
        }

    private:
        THolder<TSocketHolder> Sock_;
        TSocketIo SocketIo_;
    };


    class TKeepAliveData {
    public:
        TKeepAliveData(
            THolder<TBackendIo> backendIo,
            TSockAddrInfo addresses,
            const TSockAddr& concreteAddress
        ) noexcept
            : BackendIo_(std::move(backendIo))
            , Addresses_(std::move(addresses))
            , ConcreteAddress_(concreteAddress)
        {}

        TError SslConnect(const NSrvKernel::THttpsSettings& httpsSettings, const TRequest* req, TInstant deadline) noexcept;

        [[nodiscard]] bool CompatibleSniHost(const TRequest& req) const noexcept {
            return !SniHost_ || SniHost_ == StripString(req.Headers().GetFirstValue("host"));
        }

        [[nodiscard]] IIoInput& Input() noexcept {
            if (SslIo_) {
                return *SslIo_;
            } else {
                return BackendIo_->Input();
            }
        }

        [[nodiscard]] IIoOutput& Output() noexcept {
            if (SslIo_) {
                return *SslIo_;
            } else {
                return BackendIo_->Output();
            }
        }

        [[nodiscard]] TSocketHolder& Sock() noexcept {
            return BackendIo_->Sock();
        }

        [[nodiscard]] THolder<TSocketHolder>& SockHolder() noexcept {
            return BackendIo_->SockHolder();
        }

        [[nodiscard]] TSocketIo& SocketIo() noexcept {
            return BackendIo_->SocketIo();
        }

        [[nodiscard]] TString AddrStr() const noexcept {
            if (ConcreteAddress_) {
                return ConcreteAddress_.ToString();
            }
            return {};
        }

        [[nodiscard]] TSockAddrInfo& Addresses() noexcept {
            return Addresses_;
        }

        [[nodiscard]] TSockAddr ConcreteAddress() const noexcept {
            return ConcreteAddress_;
        }

        TInstant KeepAliveReleaseDeadline() const noexcept {
            return KeepAliveReleaseDeadline_;
        }

        void SetKeepAliveReleaseDeadline(TInstant keepAliveReleaseDeadline) noexcept {
            KeepAliveReleaseDeadline_ = keepAliveReleaseDeadline;
        }

        bool HaveSsl() const noexcept {
            return !!SslIo_;
        }

        bool IsSslShutdown() const noexcept {
            if (SslIo_) {
                return SslIo_->GetShutdown() != 0;
            }
            return false;
        }

        void SetCanStore(bool canStore) noexcept {
            CanStore_ = canStore;
        }

        bool CanStore() const noexcept {
            return CanStore_;
        }

        const TString& SniHost() const noexcept {
            return SniHost_;
        }

    private:
        THolder<TBackendIo> BackendIo_;
        TSockAddrInfo Addresses_;
        const TSockAddr ConcreteAddress_;
        THolder<TSslIo> SslIo_;
        TInstant KeepAliveReleaseDeadline_;
        TString SniHost_;
        bool CanStore_ = true;

        friend NConnectionManager::TSocketContainer;
    };

    inline bool NeedDebugLog(const TConnDescr& descr) noexcept {
        return descr.ErrorLog && descr.ErrorLog->DefaultPriority() >= TLOG_DEBUG;
    }

    [[nodiscard]] bool MayPass100Continue(const TRequest* request) noexcept;

    TErrorOr<THolder<TKeepAliveData>> EstablishConnection(
            const TConnDescr& descr,
            const THostInfo& hostInfo,
            const TBackendConfig& config,
            EPollMode pollMode,
            const TInstant start,
            TDuration& connectDuration,
            TInstant& connectDeadline,
            TInstant& effectiveSessionDeadline
    ) noexcept;
}
