#pragma once

#include "config.h"
#include "tls.h"

#include <balancer/kernel/http/parser/httpdecoder.h>
#include <balancer/kernel/http/parser/httpencoder.h>
#include <balancer/kernel/custom_io/countio.h>
#include <balancer/kernel/http2/client/http2_request.h>

namespace NModProxy {
class TTransfer;

class TModuleProxy {
    static TString LogPrefix(const NSrvKernel::TConnDescr& descr, TStringBuf host, ui16 port, NModProxy::ETransferType type) noexcept;
public:
    explicit TModuleProxy(const TProxyConfig& proxyConfig);

    TError Run(const TConnDescr& descr, TTls& tls) const noexcept;
private:
    TError HandleSrcrwr(const TConnDescr& descr, THostInfo& info) const;

    static bool IsCancelledTransfer(const TError& error) noexcept;

    struct TErrorCtx {
        TString Ip;
        TDuration ConnDuration;
        TDuration RespDuration;
        TDuration SessDuration;
        size_t BodyFromClient = 0;
        size_t RawFromBackend = 0;
        size_t RespSize = 0;
        bool TransferedWholeResponse = false;
        ui16 StatusCode = 0;
    };

    struct TErrorWithComment {
        TErrorWithComment(TError error, TStringBuf comment, uint64_t flags = 0)
            : Error(std::move(error))
            , Comment(comment)
            , Flags(flags)
        {
        }
        TError Error;
        TStringBuf Comment;
        uint64_t Flags;
        static constexpr uint64_t BackendError = 1;
        static constexpr uint64_t WriteError = 1 << 1;
        explicit constexpr operator bool() const noexcept {
            return static_cast<bool>(Error);
        }
    };

    struct TIgnoredErrors {
        // Don't forget to increase Errors capacity, if you add cases where
        // more than 3 simultaneous ignored errors may exist
        TStackVec<TErrorWithComment, 3, false> Errors;

        void Add(TErrorWithComment error) {
            Errors.push_back(std::move(error));
        }
    };

    void CollectIgnoredErrors(TTransfer& backendToClient, TTransfer& clientToBackend, TIgnoredErrors& errors) const noexcept;

    void UpdateErrorStats(const TErrorWithComment& error, const TConnDescr& descr, const TErrorCtx& ctx) const noexcept;

    void LogErrors(const TIgnoredErrors& ignoredErrors, const TErrorWithComment& error, const TConnDescr& descr, const TErrorCtx& ctx) const noexcept;

    void LogErrorSection(const TErrorWithComment& error, const TConnDescr& descr, const TErrorCtx& ctx, TString* reason = nullptr) const noexcept;

    void LogHttp2RequestSummary(const TConnDescr& descr, const NBalancerClient::THttp2RequestSummary& requestSummary) const noexcept;

    bool IsValidKeepAlive(TKeepAliveData* keepAlive, const TConnDescr& descr) const noexcept;

    TErrorOr<THolder<TKeepAliveData>> GetKeepAliveConnection(
        const TConnDescr& descr,
        TTls& tls,
        const THostInfo& hostInfo,
        const TInstant start,
        TDuration& connectDuration,
        TInstant& effectiveSessionDeadline
    ) const noexcept;

    void SetupBackendIo(
        const TConnDescr& descr,
        IIoInput* input,
        IIoOutput* output,
        TMaybe<TFromBackendDecoder>& fromBackendDecoder,
        TMaybe<TToBackendEncoder>& toBackendEncoder,
        TMaybe<TBackendInput>& backendInput,
        TMaybe<TBackendOutput>& backendOutput
    ) const noexcept;

    void PrepareRequestHeadersAndProps(
        const TConnDescr& descr
    ) const noexcept;

    TErrorWithComment WaitTransfers(
        TTransfer& backendToClient,
        TTransfer& clientToBackend,
        TCoroSingleCondVar& oneOfTransfersEnded
    ) const noexcept;

    void FinishTransfers(
        TTransfer& backendToClient,
        TTransfer& clientToBackend,
        bool alwaysCancel
    ) const noexcept;

    TErrorWithComment CheckBackendError(TTransfer& transfer, TStringBuf comment, uint64_t flags) const noexcept;

    TErrorWithComment CheckClientError(TTransfer& transfer, TStringBuf comment, uint64_t flags) const noexcept;

    TErrorWithComment CheckTransfers(
        TTransfer& backendToClient,
        TTransfer& clientToBackend,
        bool checkBackendReadError,
        bool& transferedWholeResponse
    ) const noexcept;

    TErrorWithComment DoRunTcpTransfers(
        TTransfer& backendToClient,
        TTransfer& clientToBackend,
        TCoroSingleCondVar& oneOfTransfersEnded
    ) const noexcept;

    TErrorWithComment DoRunWebsocketTransfers(
        const TConnDescr& descr,
        TFromBackendDecoder* fromBackendDecoder,
        TToBackendEncoder* toBackendEncoder,
        TTransfer& backendToClient,
        TTransfer& clientToBackend,
        TCoroSingleCondVar& oneOfTransfersEnded,
        TInstant& effectiveSessionDeadline
    ) const noexcept;

    TErrorWithComment StartPassHttpRequest(
        const TConnDescr& descr,
        TToBackendEncoder* toBackendEncoder,
        TTransfer& clientToBackend,
        TInstant effectiveSessionDeadline,
        TKeepAliveData* keepAlive
    ) const noexcept;

    bool TryRecvResponse(TFromBackendDecoder* fromBackendDecoder) const noexcept;

    TErrorWithComment HandleHttpResponse(
        const TConnDescr& descr,
        TTls& tls,
        TFromBackendDecoder* fromBackendDecoder,
        IHttpOutput* const toClientOut,
        TResponse& response,
        bool& upgradeCompleted,
        const TInstant start,
        TDuration& responseDuration,
        const TInstant effectiveSessionDeadline,
        NSrvKernel::TCountInput& countBackendInput,
        size_t* responseSize,
        TKeepAliveData* keepAlive
    ) const noexcept;

    TErrorWithComment DoRunHttpTransfers(
        const TConnDescr& descr,
        TTransfer& backendToClient,
        TTransfer& clientToBackend,
        bool watchClientClose
    ) const noexcept;

    TErrorWithComment DoRunTransfers(
        const TConnDescr& descr,
        TTls& tls,
        const THostInfo& hostInfo,
        TCountInput& countBackendInput,
        TCountInput& countClientInput,
        TKeepAliveData* keepAlive,
        TResponse& response,
        const TInstant& start,
        TInstant& effectiveSessionDeadline,
        TDuration& responseDuration,
        TIgnoredErrors& ignoredErrors,
        size_t* responseSize,
        bool& transferedWholeResponse
    ) const noexcept;

    TError Proxy(const TConnDescr& oldDescr, TTls& tls) const noexcept;

  private:
    const TString NAME;
    const NModProxy::TProxyConfig& ProxyConfig_;
};
} // namespace NModProxy
