#pragma once

#include "http2.h"
#include "http2_flow.h"
#include "http2_frame.h"
#include "http2_conn_in.h"
#include "http2_conn_out.h"
#include "http2_settings.h"
#include "http2_stream.h"
#include "http2_streams_manager.h"
#include "http2_edge_prio_fix.h"

#include <balancer/kernel/http2/server/common/http2_common.h>
#include <balancer/kernel/http2/server/hpack/hpack.h>
#include <balancer/kernel/http2/server/utils/http2_prio_tree.h>

#include <balancer/kernel/custom_io/iterator.h>
#include <balancer/kernel/module/iface.h>

#include <util/generic/bt_exception.h>
#include <util/generic/yexception.h>

namespace NSrvKernel::NHTTP2 {

    enum class EShutdownPhase {
        Running,
        GoAwayNoIdSent,
        GoAwayLastIdSent,
    };

    enum class EConnState {
        Open,
        ReadFin,
        Reset
    };

    struct TConnError {
        TGoAway GoAway;
        TConnErrorReason Reason;

        TConnError(TGoAway goAway, TConnErrorReason reason)
            : GoAway(std::move(goAway))
            , Reason(std::move(reason))
        {}

        void PrintTo(IOutputStream& out) const;
    };

    // TConnection =====================================================================================================

    class TConnection final : public IConnection {
    public:
        static void InitStatic() noexcept;

        // Called from main coroutine
        TConnection(
            const IHTTP2Module& parent,
            const TConnDescr& mainDescr,
            TLogger& logger,
            const TSettings& serverSettings,
            const TAuxServerSettings& auxServerSettings
        ) noexcept;

        // Called from main coroutine
        ~TConnection() override;

        // Called from all coroutines
        void PrintTo(IOutputStream& out) const;

        // Called from main coroutine
        TError RunConnection() noexcept;

        // Called from shutdown coroutine
        void OnShutdown() noexcept;

    private:
        // Called from main coroutine
        [[nodiscard]] TMaybe<TFrame> RecvClientFrame() noexcept;

        // Called from main coroutine
        TError ProcessClientFrame(TFrame frame) noexcept;

        // Called from main coroutine
        TError OnData(TFrame frame) noexcept;

        // Called from main coroutine
        TError OnHeaders(TFrame frame) noexcept;

        // Called from main coroutine
        TErrorOr<TChunkList> RecvFullHeaderBlock(TFrame& headersFrame) noexcept;

        // Called from main coroutine
        TError OnPriority(TFrame frame) noexcept;

        // Called from main coroutine
        TError OnRstStream(TFrame frame) noexcept;

        // Called from main coroutine
        TError OnSettings(TFrame frame) noexcept;

        // Called from main coroutine
        TError OnPing(TFrame frame) noexcept;

        // Called from main coroutine
        void OnPingAck(TPing ping) noexcept;

        // Called from main coroutine
        void OnGoAway(TFrame frame) noexcept;

        // Called from main coroutine
        TError OnWindowUpdate(TFrame frame) noexcept;

    private:
        // Called from main coroutine
        void CheckCpuLimit() noexcept;

        // Called from all coroutines
        void OnConnReset(TError err, EErrorSource ioDir) noexcept override;

        // Called from all coroutines
        void OnConnInternalError(TError err) noexcept override;

        // Called from all coroutines
        void OnConnError(EErrorCode errorCode, TStringBuf debugData, TConnErrorReason reason) noexcept override;

        // Called from all coroutines
        TError OnStreamError(ui32 streamId, EErrorCode errorCode, TStreamErrorReason reason) noexcept;

        // Called from all coroutines
        bool IsConnError() const noexcept override {
            return EConnState::Reset == ConnState_ || ConnError_ || ConnException_;
        }

    private:
        // Called from stream coroutines
        TError OnHTTP(const TConnDescr& connDescr) const override;

        // Called from stream coroutines
        void DisposeStream(TStream&) noexcept override;

    private:
        // Called from all coroutines
        void TrySetConnException(TError ptr, EErrorSource errorSource) noexcept;

        // Called from main coroutine
        [[nodiscard]] bool ProcessReadFin() noexcept;

        // Called from main coroutine
        void ProcessConnError() noexcept;

        // Called from main coroutine
        [[nodiscard]] bool IsReadOpen() const noexcept {
            return EConnState::Open == ConnState_ && !IsConnError();
        }

        // Called from all coroutines
        TConnOutput& GetClientOutput() noexcept override {
            return ClientOutput_;
        }

        // Called from main and stream coroutines
        TPrioTreeNode& GetPrioTreeRoot() noexcept override {
            return PrioTreeRoot_;
        }

        // Called from main and stream coroutines
        TEdgePrioFix& GetEdgePrioFix() noexcept override {
            return EdgePrioFix_;
        }

        // Called from all coroutines
        const TClientSettings& GetClientSettings() const noexcept override {
            return ClientSettings_;
        }

        // Called from all coroutines
        const TServerSettings& GetServerSettings() const noexcept override {
            return ServerSettings_;
        }

        // Called from all coroutines
        const TAuxServerSettings& GetAuxServerSettings() const noexcept override {
            return AuxServerSettings_;
        }

        // Called from main coroutine
        ui32 GetMaxClientStreamId() const noexcept override {
            return MaxClientStreamId_;
        }

        // Called from all coroutines
        const TConnDescr& GetMainDescr() const noexcept override {
            return MainDescr_;
        }

        // Called from all coroutines
        TCont& GetMainCont() noexcept override {
            return MainCont_;
        }

        // Called from all coroutines
        TContExecutor& GetExecutor() noexcept override {
            return Executor_;
        }

        // Called from all coroutines
        TStats& GetStats() noexcept override {
            return Stats_;
        }

        // Called from all coroutines
        TLogger& GetLogger() noexcept override {
            return Logger_;
        }

        // Called from output coroutine
        TSocketIo* GetSocketIo() noexcept override {
            return GetMainDescr().Properties->SocketIo;
        }

    private:
        const IHTTP2Module& Parent_;
        const TConnDescr& MainDescr_;
        TCont& MainCont_;
        TLogger& Logger_;
        TStats& Stats_;

        // The remote side's settings
        TClientSettings ClientSettings_;
        // The local settings
        TServerSettings ServerSettings_;
        // The local settings beyound the RFC. We make a local copy to avoid changes during the connection lifetime.
        const TAuxServerSettings AuxServerSettings_;

        // Receiving from the remote peer
        TConnInput ClientInput_;
        // Sending to the remote peer via a separate coroutine
        TConnOutput ClientOutput_;

        // Decodes incoming headers
        THPackDecoder HPackDecoder_;
        // The local connection flow window
        TConnRecvFlow RecvFlow_;

        TPrioTreeNode PrioTreeRoot_;
        TEdgePrioFix EdgePrioFix_;
        TStreamsManager Streams_;

        TMaybe<TConnError> ConnError_;
        TError ConnException_;

        TDuration LastRTT_;

        ui32 MaxClientStreamId_ = 0;
        ui32 StreamsCnt_ = 0;

        EShutdownPhase ShutdownPhase_ = EShutdownPhase::Running;
        EConnState ConnState_ = EConnState::Open;
        TContExecutor& Executor_;
    };
}
