#pragma once

#include "http2_base.h"
#include "http2_frame.h"

#include <balancer/kernel/http2/server/common/http2_common.h>
#include <balancer/kernel/stats/manager.h>

#include <util/generic/xrange.h>
#include <util/string/cast.h>
#include <util/string/builder.h>
#include <util/system/defaults.h>


namespace NSrvKernel::NHTTP2 {

    class TStatCounter {
    public:
        TStatCounter(const TStatCounter& other, size_t workerId) noexcept;

        template <class TVal>
        TStatCounter& operator += (TVal val) noexcept {
            ValueShared_.Add(val);
            return *this;
        }

        template <class TVal>
        TStatCounter& operator -= (TVal val) noexcept {
            ValueShared_.Sub(val);
            return *this;
        }

    protected:
        TStatCounter(
            TSharedStatsManager& statsManager,
            TString unistatName,
            TString unistatSigopt) noexcept;

    private:
        TSharedCounter ValueShared_;
    };

    class TDeltaCounter : public TStatCounter {
    public:
        using TStatCounter::TStatCounter;

        explicit TDeltaCounter(TSharedStatsManager& statsManager, TString name) noexcept;
    };

    class TErrorCounter : public TStatCounter {
    public:
        using TStatCounter::TStatCounter;

        explicit TErrorCounter(TSharedStatsManager& statsManager, TString name, TString err, bool attr) noexcept;
    };

    class TAbsCounter : public TStatCounter {
    public:
        using TStatCounter::TStatCounter;

        explicit TAbsCounter(TSharedStatsManager& statsManager, TString name) noexcept;
    };


    class TConnCounters {
        TDeltaCounter Open_;
        TDeltaCounter OpenActive_;
        TDeltaCounter Close_;
        TDeltaCounter Abort_;
        TAbsCounter InProg_;
        TAbsCounter Active_;

    public:
        explicit TConnCounters(TSharedStatsManager& statsManager, TString prefix) noexcept;

        TConnCounters(const TConnCounters& other, size_t workerId) noexcept;

        void OnOpen() noexcept {
            Open_ += 1;
            InProg_ += 1;
        }

        void OnFirstStream() noexcept {
            OpenActive_ += 1;
        }

        void OnClose() noexcept {
            Close_ += 1;
            InProg_ -= 1;
        }

        void OnAbort() noexcept {
            Abort_ += 1;
            InProg_ -= 1;
        }

        void OnActive() noexcept {
            Active_ += 1;
        }

        void OnInactive() noexcept {
            Active_ -= 1;
        }
    };


    class TStreamCounters {
        TDeltaCounter UnknownErrors_;
        TDeltaCounter ClientOpen_;
        TDeltaCounter ServerOpen_;
        TDeltaCounter EndRecv_;
        TDeltaCounter EndSend_;
        TDeltaCounter Success_;
        TDeltaCounter ConnAbort_;
        TDeltaCounter Dispose_;
        TAbsCounter InProg_;

    public:
        TStreamCounters(TSharedStatsManager& statsManager) noexcept;

        TStreamCounters(const TStreamCounters& other, size_t workerId) noexcept;

        void OnClientOpen() noexcept {
            ClientOpen_ += 1;
            InProg_ += 1;
        }

        void OnClientClose() noexcept {
            EndRecv_ += 1;
        }

        void OnServerClose() noexcept {
            EndSend_ += 1;
        }

        void OnSuccess() noexcept {
            Success_ += 1;
        }

        void OnDisposal() noexcept {
            Dispose_ += 1;
            InProg_ -= 1;
        }

        void OnConnAbort() noexcept {
            ConnAbort_ += 1;
        }

        void OnUnknownError() noexcept {
            UnknownErrors_ += 1;
        }
    };


    template <class TCode>
    class TErrorCounters {
        TVector<TErrorCounter> Error_;

    public:
        explicit TErrorCounters(TSharedStatsManager& statsManager, TString name, bool attr) noexcept
            : Error_(Gen(statsManager, name, attr))
        {}

        TErrorCounters(const TErrorCounters& other, size_t workerId)
            : Error_(MakeThreadLocalCounters(other.Error_, workerId))
        {}

        bool OnError(TCode err) noexcept {
            err = TCode(std::min((ui32)err, (ui32)TCode::UNKNOWN));
            Error_[(ui32)err] += 1;
            return err != TCode::UNKNOWN;
        }

    private:
        static TVector<TErrorCounter> Gen(TSharedStatsManager& statsManager, TString name, bool attr) noexcept {
            TVector<TErrorCounter> res;
            for (ui32 cnt : xrange((ui32)TCode::UNKNOWN + 1)) {
                res.emplace_back(statsManager, name, (TStringBuilder() << (TCode) cnt), attr);
            }
            return res;
        }

        static TVector<TErrorCounter> MakeThreadLocalCounters(const TVector<TErrorCounter>& masterCounters, size_t workerId) noexcept {
            TVector<TErrorCounter> counters;
            counters.reserve(masterCounters.size());
            for (const auto &counter : masterCounters) {
                counters.emplace_back(counter, workerId);
            }
            return counters;
        }
    };


    class TGoAwaySendCounters {
        TErrorCounters<EErrorCode> GoAwaySend_;
        TErrorCounters<EErrorCode> UnknownGoAwaySend_;

        TErrorCounters<EConnNoError> NoErrorSend_;
        TErrorCounters<EConnProtocolError> ProtocolErrorSend_;
        TErrorCounters<EConnInternalError> InternalErrorSend_;
        TErrorCounters<EFlowControlError> FlowControlErrorSend_;
        TErrorCounters<EConnStreamClosed> StreamClosedErrorSend_;
        TErrorCounters<ECompressionError> CompressionErrorSend_;

    public:
        TGoAwaySendCounters(TSharedStatsManager& statsManager)
            : GoAwaySend_(statsManager, "go_away_send", true)
            , UnknownGoAwaySend_(statsManager, "unknown_go_away_send", true)
            , NoErrorSend_(statsManager, "conn_no_error_send", true)
            , ProtocolErrorSend_(statsManager, "conn_protocol_error_send", true)
            , InternalErrorSend_(statsManager, "conn_internal_error_send", true)
            , FlowControlErrorSend_(statsManager, "conn_flow_control_error_send", true)
            , StreamClosedErrorSend_(statsManager, "conn_stream_closed_error_send", true)
            , CompressionErrorSend_(statsManager, "compression_error_send", true)
        {}

        TGoAwaySendCounters(const TGoAwaySendCounters& other, size_t workerId);

        void OnError(const TGoAway& goAway, TConnErrorReason errorReason) {
            std::visit([&](auto reason) {
                using TReason = std::decay_t<decltype(reason)>;
                if constexpr (std::is_same_v<TReason, EConnNoError>) {
                    NoErrorSend_.OnError(reason);
                } else if constexpr (std::is_same_v<TReason, EConnProtocolError>) {
                    ProtocolErrorSend_.OnError(reason);
                } else if constexpr (std::is_same_v<TReason, EConnInternalError>) {
                    InternalErrorSend_.OnError(reason);
                } else if constexpr (std::is_same_v<TReason, EFlowControlError>) {
                    FlowControlErrorSend_.OnError(reason);
                } else if constexpr (std::is_same_v<TReason, EConnStreamClosed>) {
                    StreamClosedErrorSend_.OnError(reason);
                } else if constexpr (std::is_same_v<TReason, ECompressionError>) {
                    CompressionErrorSend_.OnError(reason);
                } else if constexpr (!std::is_same_v<TReason, TUnspecifiedReason>) {
                    if (!IsIn({
                        EErrorCode::NO_ERROR,
                        EErrorCode::PROTOCOL_ERROR,
                        EErrorCode::INTERNAL_ERROR,
                        EErrorCode::FLOW_CONTROL_ERROR,
                        EErrorCode::STREAM_CLOSED,
                        EErrorCode::COMPRESSION_ERROR,
                    }, goAway.ErrorCode)) {
                        UnknownGoAwaySend_.OnError(goAway.ErrorCode);
                    }
                }
            }, errorReason);
            GoAwaySend_.OnError(goAway.ErrorCode);
        }
    };


    class TRstStreamSendCounters {
        TErrorCounters<EErrorCode> RstStreamSend_;
        TErrorCounters<EErrorCode> UnknownRstStreamSend;

        TErrorCounters<EStreamNoError> NoErrorSend_;
        TErrorCounters<EStreamProtocolError> ProtocolErrorSend_;
        TErrorCounters<EStreamInternalError> InternalErrorSend_;
        TErrorCounters<EFlowControlError> FlowControlErrorSend_;
        TErrorCounters<EStreamStreamClosed> StreamClosedErrorSend_;
        TErrorCounters<ERefusedStream> RefusedStreamErrorSend_;

    public:
        TRstStreamSendCounters(TSharedStatsManager& statsManager)
            : RstStreamSend_(statsManager, "rst_stream_send", true)
            , UnknownRstStreamSend(statsManager, "unknown_rst_stream_send", true)
            , NoErrorSend_(statsManager, "stream_no_error_send", true)
            , ProtocolErrorSend_(statsManager, "stream_protocol_error_send", true)
            , InternalErrorSend_(statsManager, "stream_internal_error_send", true)
            , FlowControlErrorSend_(statsManager, "stream_flow_control_error_send", true)
            , StreamClosedErrorSend_(statsManager, "stream_stream_closed_error_send", true)
            , RefusedStreamErrorSend_(statsManager, "refused_stream_error_send", true)
        {}

        TRstStreamSendCounters(const TRstStreamSendCounters& other, size_t workerId);

        void OnError(EErrorCode errorCode, TStreamErrorReason errorReason) noexcept {
            std::visit([&](auto reason) {
                using TReason = std::decay_t<decltype(reason)>;
                if constexpr (std::is_same_v<TReason, EStreamNoError>) {
                    NoErrorSend_.OnError(reason);
                } else if constexpr (std::is_same_v<TReason, EStreamProtocolError>) {
                    ProtocolErrorSend_.OnError(reason);
                } else if constexpr (std::is_same_v<TReason, EStreamInternalError>) {
                    InternalErrorSend_.OnError(reason);
                } else if constexpr (std::is_same_v<TReason, ERefusedStream>) {
                    RefusedStreamErrorSend_.OnError(reason);
                } else if constexpr (std::is_same_v<TReason, EFlowControlError>) {
                    FlowControlErrorSend_.OnError(reason);
                } else if constexpr (std::is_same_v<TReason, EStreamStreamClosed>) {
                    StreamClosedErrorSend_.OnError(reason);
                } else if constexpr (!std::is_same_v<TReason, TUnspecifiedReason>) {
                    if (!IsIn({
                        EErrorCode::NO_ERROR,
                        EErrorCode::PROTOCOL_ERROR,
                        EErrorCode::INTERNAL_ERROR,
                        EErrorCode::FLOW_CONTROL_ERROR,
                        EErrorCode::STREAM_CLOSED,
                        EErrorCode::REFUSED_STREAM,
                    }, errorCode)) {
                        UnknownRstStreamSend.OnError(errorCode);
                    }
                }
            }, errorReason);
            RstStreamSend_.OnError(errorCode);
        }
    };


    class TGoAwayRecvCounters {
        TString Prefix_;
        TErrorCounters<EErrorCode> GoAwayRecv_;
        TErrorCounter FailedPing_;
        TErrorCounter Error102ReadingFromSocket_;
        TErrorCounter ClosingCurrentSessions_;

    public:
        TGoAwayRecvCounters(TSharedStatsManager& statsManager)
            : Prefix_("go_away_recv")
            , GoAwayRecv_(statsManager, Prefix_, true)
            , FailedPing_(statsManager, Prefix_, "PROTOCOL_ERROR_Failed_ping", true)
            , Error102ReadingFromSocket_(statsManager, Prefix_, "PROTOCOL_ERROR_Error_102_reading_from_socket", true)
            , ClosingCurrentSessions_(statsManager, Prefix_, "PROTOCOL_ERROR_Closing_current_sessions", true)
        {}

        TGoAwayRecvCounters(const TGoAwayRecvCounters& other, size_t workerId);

        void OnGoAway(const TGoAway& goAway) noexcept;
    };


    class TRstStreamRecvCounters {
        TString Prefix_;
        TErrorCounters<EErrorCode> RstStreamRecv_;
        TErrorCounter ProtocolErrorAfterCancel_;

    public:
        TRstStreamRecvCounters(TSharedStatsManager& statsManager)
            : Prefix_("rst_stream_recv")
            , RstStreamRecv_(statsManager, Prefix_, true)
            , ProtocolErrorAfterCancel_(statsManager, Prefix_, "PROTOCOL_ERROR_after_CANCEL", true)
        {}

        TRstStreamRecvCounters(const TRstStreamRecvCounters& other, size_t workerId);

        void OnError(EErrorCode errorCode) {
            RstStreamRecv_.OnError(errorCode);
        }

        void OnProtocolErrorAfterCancel() noexcept {
            ProtocolErrorAfterCancel_ += 1;
        }
    };


    enum class EIOError : ui32 {
        Cancelled /* "cancel" */,
        ConnReset /* "rst" */,
        IO /* "eio" */,
        Pipe /* "pipe" */,
        SSL /* "ssl" */,
        Other /* "other" */,
        UNKNOWN,
    };


    struct TBeforePrefaceErrorCounters {
        TString Prefix_;
        TErrorCounters<EIOError> BeforePreface_;
        TErrorCounter ClientFin_;

    public:
        TBeforePrefaceErrorCounters(TSharedStatsManager& statsManager)
            : Prefix_("before_preface")
            , BeforePreface_(statsManager, Prefix_, false)
            , ClientFin_(statsManager, Prefix_, "fin", false)
        {}

        TBeforePrefaceErrorCounters(const TBeforePrefaceErrorCounters& other, size_t workerId);

        bool OnError(EIOError err) noexcept {
            return BeforePreface_.OnError(err);
        }

        void OnClientFin() noexcept {
            ClientFin_ += 1;
        }
    };


    EIOError ErrnoToIOError(int err) noexcept;


    class TStats {
    public:
        TStats(TSharedStatsManager& statsManager)
            : H1Conn(statsManager, "h1")
            , H2Conn(statsManager, "h2")
            , Stream(statsManager)
            , GoAwaySend(statsManager)
            , GoAwayRecv(statsManager)
            , RstStreamSend(statsManager)
            , RstStreamRecv(statsManager)
            , BeforePreface(statsManager)
            , RecvFrame(statsManager, "recv_frame", false)
            , SendFrames(statsManager, "send_frames", false)
            , Unexpected(statsManager, "unexpected", false)
            , ReqsWithHeadersDropped(statsManager, "reqs_with_headers_dropped")
            , RespsWithHeadersDropped(statsManager, "resps_with_headers_dropped")
        {}

        TStats(const TStats& other, size_t workerId);

        TConnCounters H1Conn;
        TConnCounters H2Conn;

        TStreamCounters Stream;

        TGoAwaySendCounters GoAwaySend;
        TGoAwayRecvCounters GoAwayRecv;

        TRstStreamSendCounters RstStreamSend;
        TRstStreamRecvCounters RstStreamRecv;

        TBeforePrefaceErrorCounters BeforePreface;
        TErrorCounters<EIOError> RecvFrame;
        TErrorCounters<EIOError> SendFrames;
        TErrorCounters<EIOError> Unexpected;

        TDeltaCounter ReqsWithHeadersDropped;
        TDeltaCounter RespsWithHeadersDropped;

    public:
        bool OnIOError(EIOError err, EErrorSource src) noexcept;
    };
}
