#pragma once

#include "enums.h"

#include <balancer/kernel/cookie/set_cookie.h>
#include <balancer/kernel/cookie/utils/utils.h>
#include <balancer/kernel/http/parser/http.h>
#include <balancer/kernel/module/module_face.h>
#include <balancer/kernel/module/conn_descr.h>
#include <balancer/kernel/regexp/regexp_pire.h>

namespace NModCookiePolicy {
    using namespace NSrvKernel;

    struct [[nodiscard]] TRequestCookie {
        TNameValue NameValue;
        TBlob Memory;
        bool Added = false;
    };


    template <class TValue>
    struct [[nodiscard]] TExtractedRequestCookie {
        TMaybe<TValue> Parsed;
        ui32 Count = 0;
        bool Conflict = false;

        Y_BALANCER_TUPLE_EQ(TExtractedRequestCookie<TValue>, Parsed, Count, Conflict)

        bool BadParse() const noexcept {
            return Count && !Parsed;
        }

        void PrintTo(IOutputStream& out) const noexcept {
            out << "{.Parsed="sv;
            NCookie::PrintTo(out, Parsed);
            out << ",.Count="sv << Count;
            out << ",.Conflict="sv << Conflict;
            out << "}"sv;
        }
    };


    template <class TVal, class TMerger>
    TMaybe<TVal> MergeCookieValues(TMaybe<TVal> prev, TMaybe<TVal> next, TMerger&& merger) noexcept {
        if (!prev && !next) {
            return Nothing();
        }
        if (!prev) {
            return next;
        }
        if (!next) {
            return prev;
        }
        return merger(*prev, *next);
    }


    template <class TValue, class TParser, class TMerger>
    TExtractedRequestCookie<TValue> ExtractRequestCookie(
        const TVector<TRequestCookie>& cookies, TStringBuf name, TParser&& parser, TMerger&& merger) noexcept
    {
        TExtractedRequestCookie<TValue> res;
        for (auto&& c : cookies) {
            if (c.NameValue.Name == name) {
                TMaybe<TValue> parsed = parser(c.NameValue.Value);
                if (res.Count) {
                    res.Conflict |= (res.Parsed != parsed);
                    if (res.Conflict) {
                        res.Parsed = MergeCookieValues(res.Parsed, parsed, std::forward<TMerger>(merger));
                    }
                } else {
                    res.Parsed = parsed;
                }
                res.Count += 1;
            }
        }
        return res;
    }


    // The wrapper could have been simplier but TStringStorage is suboptimal.

    class [[nodiscard]] TResponseCookie;


    class [[nodiscard]] TResponseCookieView {
    public:
        TResponseCookieView(TResponseCookie& h) noexcept;

        inline const TSetCookie& Const() const noexcept;

        inline TSetCookie& Mutable() noexcept;

    private:
        TResponseCookie& H_;
    };


    class [[nodiscard]] TResponseCookie {
    public:
        struct TAdded {};

        TResponseCookie(size_t sourceIdx, TSetCookie::TParseResult c) noexcept
            : Cookie_(c)
            , SourceIdx_(sourceIdx)
        {}

        TResponseCookie(TAdded, TSetCookie c) noexcept
            : Cookie_(c)
            , Added_(true)
        {}

        TResponseCookie(TSetCookie c) noexcept
            : Cookie_(c)
        {}

        Y_BALANCER_TUPLE_EQ(TResponseCookie, Cookie_, SourceIdx_, Added_);

        TResponseCookieView View() noexcept {
            return {*this};
        }

        TSetCookie::TParseResult& CookieMutable() noexcept {
            SourceIdx_.Clear();
            return Cookie_;
        }

        const TSetCookie::TParseResult& Cookie() const noexcept {
            return Cookie_;
        }

        TMaybe<size_t> SourceIdx() const noexcept {
            return SourceIdx_;
        }

        bool IsAddedHeader() const noexcept {
            return Added_;
        }

    private:
        TSetCookie::TParseResult Cookie_;
        TMaybe<size_t> SourceIdx_;
        bool Added_ = false;
    };


    inline TResponseCookieView::TResponseCookieView(TResponseCookie& h) noexcept
        : H_(h)
    {
        Y_VERIFY(std::holds_alternative<TSetCookie>(h.Cookie()));
    }

    inline const TSetCookie& TResponseCookieView::Const() const noexcept {
        return std::get<TSetCookie>(H_.Cookie());
    }

    inline TSetCookie& TResponseCookieView::Mutable() noexcept {
        return std::get<TSetCookie>(H_.CookieMutable());
    }

    [[nodiscard]]
    bool TruncateDeletions(TVector<TSetCookie>& dels, ui32 hardMax) noexcept;
}
