#pragma once

#include "http2.h"
#include "http2_base.h"
#include "http2_flow.h"
#include "http2_headers.h"
#include "http2_conn_out.h"
#include "http2_stream_in.h"
#include "http2_stream_out.h"

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

#include <balancer/kernel/custom_io/queue.h>
#include <balancer/kernel/io/iobase.h>
#include <balancer/kernel/memory/alloc.h>

#include <util/string/builder.h>

namespace NSrvKernel::NHTTP2 {

    class TStream final : public TStreamId
                        , public TSimpleRefCount<TStream>
                        , private IIoInput
                        , private IHttpOutput
                          // Simplifying the Idle/Close queue membership management.
                          // Completely safe: unlinks itself in destructor.
                        , private TIntrusiveListItem<TStream>

    {
        friend class TStreamsManager;
        friend class TIntrusiveList<TStream>;
        friend class TIntrusiveListItem<TStream>;

    public:
        enum class EParserState {
            Start,
            Headers,
            Headers100Continue,
            End,
        };

        struct TBody {
            THTTP11Request Request;

            TStreamInput ClientInput;
            TStreamOutput ClientOutput;

            size_t SendBufferMax = IMPL_DATA_FRAME_SIZE_MAX_MIN;
            EParserState ParserState = EParserState::Start;
            bool IdempotentRequest = false;

            TBody(TStream& stream, IConnection& conn, THTTP11Request request) noexcept;
        };

    public:
        using TPtr = TIntrusivePtr<TStream>;

        using EFinalState = THTTP2StreamDetails::EFinalState;

        enum EExcState {
            TopLevel,
            Backend,
            FinalFlush
        };

        explicit TStream(IConnection& conn, ui32 streamId) noexcept;

        ~TStream();

        TError Open(TFrameHeading headersHeading, THTTP11Request request, size_t sendBufferMax) noexcept;

        [[nodiscard]] bool IsCancelledByClient() const noexcept {
            return Details_.IsCancelledByClient();
        }

        [[nodiscard]] bool IsReset() const noexcept {
            return Details_.IsReset() || Details_.IsConnCancel();
        }

        [[nodiscard]] bool IsClosed() const noexcept {
            return EFinalState::Unknown != Details_.FinalState;
        }

        [[nodiscard]] bool IsIdle() const noexcept {
            return EFinalState::Unknown == Details_.FinalState && !BodyPtr_;
        }

        [[nodiscard]] bool IsOpen() const noexcept {
            return !IsIdle() && !IsClosed();
        }

        [[nodiscard]] bool IsClosedByClient() const noexcept;

        void PrintTo(IOutputStream& out) const;

        TError OnStreamError(std::pair<EErrorCode, TStreamErrorReason> error) noexcept;

    public: // recving
        TError DoRecv(TChunkList& lst, TInstant deadline) noexcept override;

        TError OnData(TFrame frame) noexcept;

        TError OnTrailers(THeadersList) noexcept;

        TError OnPriority(TPriority prio, TStream::TPtr parent) noexcept;

        void OnClientRstStream(EErrorCode code) noexcept;

        void OnConnError(const TError& connError) noexcept;

        void OnReadFin() noexcept;

    public: // sending
        TError DoSendHead(TResponse&&, const bool, TInstant) override;

        TError DoSend(TChunkList lst, TInstant) noexcept override;

        TError DoSendTrailers(THeaders&& headers, TInstant) override;

        TError OnInitialWindowUpdate() noexcept;

        TError OnWindowUpdate(ui32 windowUpdate) noexcept;

        void Start(TContExecutor& exec) noexcept;

        [[nodiscard]] bool Running() const noexcept {
            return StreamTask_.Running();
        }

        void Cancel() noexcept;

        void Join() noexcept;

    private:
        void OnFinish() noexcept;

        void OnException(TError err, EExcState excState) noexcept;

        std::pair<EErrorCode, TStreamErrorReason> GetStreamBackendError() const noexcept;

    private:
        const TString TaskName_;
        IConnection& Conn_;
        TLogger& Logger_;
        TStats& Stats_;
        const TAuxServerSettings& AuxServerSettings_;
        TPrioTreeNode PrioTreeNode_;

        // OnFinish is the only place which zeroes BodyPtr. Anyway, be aware eventually it will zero.
        THolder<TBody> BodyPtr_;

        THTTP2StreamDetails Details_;
        bool Finished_ = false;
        TCoroutine StreamTask_;
    };
}
