#pragma once

#include "http2_frame.h"
#include "http2_stats.h"

#include <balancer/kernel/http2/server/utils/http2_log.h>
#include <balancer/kernel/http2/server/hpack/hpack.h>
#include <balancer/kernel/http2/server/common/http2_headers_fsm.h>

#include <balancer/kernel/http/parser/http.h>
#include <balancer/kernel/module/iface.h>

namespace NSrvKernel::NHTTP2 {
    class TAuxServerSettings;

    // TODO (velavokr): unit-tests, fuzzing

    class THTTP11Request final : public TRequest {
    public:
        void PrintTo(IOutputStream& out, bool onlySpec = false) const;

    public:
        TChunkList Scheme;
    };

    template <class TKey, class TValue>
    static void PrintHeader(IOutputStream& out, const TKey& key, const TValue& value, bool redacted) {
        out << key << ": ";
        if (redacted) {
            out << "[REDACTED, Len=" << value << "]";
        } else {
            out << value;
        };
        out << "[CRLF]";
    }

    template <class T>
    class THeadersPrinter {
    public:
        THeadersPrinter(const T& headers, bool onlySpec, bool response) noexcept
            : Headers_(headers)
            , OnlySpec_(onlySpec)
            , Response_(response)
        {}

        void PrintTo(IOutputStream& out) const {
            for (auto& header : Headers_) {
                if constexpr(std::is_same_v<T, THeadersList>) {
                    PrintToHelper(out, header.Key, header.Value);
                } else {
                    for (auto& headerValue : header.second) {
                        PrintToHelper(out, header.first.AsStringBuf(), headerValue.AsStringBuf());
                    }
                }
            }
        }

        template <typename TKey, typename TValue>
        void PrintToHelper(IOutputStream& out, const TKey& key, const TValue& value) const {
            if (Response_) {
                if (OnlySpec_) {
                    if (IsResponseHeaderSpecial(key) || key == "Content-Length") {
                        PrintHeader(out, key, value, false);
                    }
                } else {
                    bool needRedact;
                    if constexpr (std::is_convertible_v<TKey, TStringBuf>) {
                        needRedact = AsciiEqualsIgnoreCase(key, "set-cookie");
                    } else {
                        needRedact = AsciiEqualsIgnoreCase(Union(key)->AsStringBuf(), "set-cookie");
                    }

                    PrintHeader(out, key, value, needRedact);
                }
            } else {
                if (OnlySpec_) {
                    if (IsRequestHeaderSpecial(key)
                        || key == "user-agent"
                        || key == "content-length")
                    {
                        PrintHeader(out, key, value, false);
                    }
                } else {
                    const bool needRedact = key == "cookie";
                    PrintHeader(out, key, value, needRedact);
                }
            }
        }

    private:
        const T& Headers_;
        const bool OnlySpec_;
        const bool Response_;
    };

    TString PrintReqSpecHeaders(const THeadersList& headers) noexcept;

    TString PrintReqAllHeaders(const THeadersList& headers) noexcept;

    TString PrintRespSpecHeaders(const THeadersList& headers) noexcept;

    TString PrintRespAllHeaders(const THeadersList& headers) noexcept;

    TString PrintReqSpecHeaders(const THTTP11Request& headers) noexcept;

    TString PrintReqAllHeaders(const THTTP11Request& headers) noexcept;


    TErrorOr<THTTP11Request> ConvertRequest2To11(
        TLogger& logger, TStats& stats, THeadersList headers, bool endOfStream, bool allowTEHeader) noexcept;

    [[nodiscard]] THeadersList ConvertResponse11To2(TLogger& logger, TStats& stats, TResponse response) noexcept;

    [[nodiscard]] bool ContainsSpecHeaders(const THeadersList& headers) noexcept;

    [[nodiscard]] bool Is1xxInformational(const TResponse& response) noexcept;

    [[nodiscard]] EErrorCode GetStreamErrorCode(HttpCodes errorCode) noexcept;

    [[nodiscard]] TStreamErrorReason GetStreamErrorReason(HttpCodes errorCode) noexcept;

    [[nodiscard]] ui64 size(const THeadersList& headers) noexcept;
}
