#pragma once

#include <balancer/kernel/helpers/errors.h>
#include <balancer/kernel/http2/server/common/http2_common.h>
#include <balancer/kernel/memory/chunks.h>

#include <util/digest/numeric.h>
#include <util/generic/bt_exception.h>
#include <util/generic/buffer.h>
#include <util/generic/array_ref.h>
#include <util/generic/strbuf.h>
#include <util/generic/string.h>
#include <util/memory/blob.h>
#include <util/system/defaults.h>
#include <util/str_stl.h>

#include <algorithm>
#include <tuple>
#include <utility>

namespace NSrvKernel::NHTTP2 {

    enum EFieldFlags : ui8 {
        FF_TABLE_SIZE_UPDATE_MASK = 0b1110'0000,
        FF_TABLE_SIZE_UPDATE_FLAG = 0b0010'0000,

        FF_STRING_HUFFMAN_MASK = 0b1000'0000,
        FF_STRING_HUFFMAN_ON_FLAG = 0b1000'0000,
        FF_STRING_HUFFMAN_OFF_FLAG = 0b0000'0000,

        FF_HEADER_FIELD_INDEXED_ALL_MASK = 0b1000'0000,
        FF_HEADER_FIELD_INDEXED_ALL_FLAG = 0b1000'0000,

        FF_HEADER_FIELD_INDEX_MASK = 0b1100'0000,
        FF_HEADER_FIELD_INDEX_FLAG = 0b0100'0000,

        FF_HEADER_FIELD_NO_INDEX_MASK = 0b1111'0000,

        FF_HEADER_FIELD_DO_NOT_INDEX_FLAG = 0b0000'0000,
        FF_HEADER_FIELD_NEVER_INDEX_FLAG = 0b0001'0000,
    };


    enum class EHeaderFieldType {
        ToIndex,
        NotToIndex,
        NeverIndex,
        IndexedAll,
    };


    // THPackSettings ==================================================================================================

    class THPackBaseSettings {
    public:
        ui32 MaxHeaderTableSize = RFC_HEADER_TABLE_SIZE_DEFAULT;
    };


    class THPackDecoderSettings : public THPackBaseSettings {
    public:
        ui32 MaxHeaderListSize = IMPL_MAX_HEADER_LIST_SIZE_DEFAULT;
        ui32 MaxHeadersCount = HTTP_MAX_HEADERS_COUNT_DEFAULT;

    public:
        THPackDecoderSettings() = default;

        TString ToString() const noexcept;
    };


    class THPackEncoderSettings : public THPackBaseSettings {
    public:
        ui32 MaxFrameSize = RFC_MAX_FRAME_SIZE_DEFAULT;
        EHPackHeaderRule DefaultHeaderRule = EHPackHeaderRule::Index;
        bool EnableHuffman = true;

    public:
        THPackEncoderSettings() = default;

        TString ToString() const noexcept;
    };


    // THPackLimits ====================================================================================================

    class THPackLimits {
        const THPackDecoderSettings Settings;
        size_t HeaderListSize = 0;
        size_t HeadersCount = 0;

    public:
        explicit THPackLimits(const THPackDecoderSettings settings) noexcept
            : Settings(settings)
        {}

        TError AcceptLength(size_t len) noexcept {
            Y_PROPAGATE_ERROR(CheckLength(len));
            AddLength(len);
            return {};
        }

        TError AcceptHeader() noexcept {
            Y_REQUIRE(HeadersCount < Settings.MaxHeadersCount,
                TConnectionError(EErrorCode::PROTOCOL_ERROR, EConnProtocolError::TooManyHeaders));
            HeadersCount += 1;
            return {};
        }

        TError CheckLength(size_t len) const noexcept {
            Y_REQUIRE(len <= GetAllowedLength(),
                TConnectionError(EErrorCode::PROTOCOL_ERROR, EConnProtocolError::TooBigHeaders));
            return {};
        }

        void AddLength(size_t len) noexcept {
            HeaderListSize += len + RFC_HEADER_FIELD_OVERHEAD;
        }

        [[nodiscard]] size_t GetAllowedLength() const noexcept {
            return std::max<size_t>(HeaderListSize, Settings.MaxHeaderListSize) - HeaderListSize;
        }

        [[nodiscard]] bool IsNextHeaderAllowed() const noexcept {
            return HeadersCount < Settings.MaxHeadersCount;
        }
    };


    // THeaderFieldView ================================================================================================

    struct THeaderFieldView {
         TStringBuf Name;
         TStringBuf Value;
         bool HasValue = 0;

        THeaderFieldView(TStringBuf name) noexcept
            : Name(name)
            , HasValue(false)
        {}

        THeaderFieldView(TStringBuf name, TStringBuf value) noexcept
            : Name(name)
            , Value(value)
            , HasValue(true)
        {}

        THeaderFieldView() noexcept = default;

        THeaderFieldView& operator=(const THeaderFieldView&) noexcept = default;

        auto AsTuple() const noexcept {
            return std::make_tuple(Name, Value, HasValue);
        }

        bool operator==(const THeaderFieldView& other) const noexcept;

        size_t Hash() const noexcept;
    };


    template <class T>
    struct THashOf {
        size_t operator() (const T& t) const noexcept {
            return t.Hash();
        }
    };


    using THeaderField = std::pair<TChunkPtr, TChunkPtr>;
}
