#include "cookie_policy.h"
#include "parse_render.h"
#include "stats.h"

#include <balancer/kernel/cookie/obfuscation.h>

namespace NModCookiePolicy {
    TVector<TRequestCookie> ParseRequestCookies(TArrayRef<const TStringStorage> rawHeaders) noexcept {
        TVector<TRequestCookie> cookies;
        for (auto&& c : rawHeaders) {
            EveryCookieKV([&](TNameValue nv) {
                cookies.emplace_back(TRequestCookie{.NameValue=nv});
            }, c.AsStringBuf());
        }
        return cookies;
    }


    TString RenderRequestCookies(const TVector<TRequestCookie>& cookies) noexcept {
        size_t sz = 0;
        for (auto&& c : cookies) {
            sz += c.NameValue.Name.GetOrElse({}).size() + 1 + c.NameValue.Value.size() + 2;
        }
        TString res(Reserve(sz));
        for (auto&& c : cookies) {
            AppendCookieKV(res, c.NameValue);
        }
        return res;
    }


    std::tuple<TVector<TResponseCookie>, TSetCookieParserErrors> ParseResponseCookies(
        TArrayRef<const TStringStorage> rawHeaders, bool dryRun, TGlobalStats& stats) noexcept
    {
        stats.ParserTotal.Add(1);

        if (rawHeaders.empty()) {
            stats.ParserSkip.Add(1);
            return {};
        }

        TVector<TResponseCookie> parsedHeaders(Reserve(rawHeaders.size()));
        TSetCookieParserErrors errors;

        for (auto i : xrange(rawHeaders.size())) {
            stats.ParserCookieTotal.Add(1);

            auto rawSetCookie = rawHeaders[i].AsStringBuf();
            auto parseRes = TSetCookie::Parse(NCookie::FromStringBuf(rawSetCookie));

            if (std::holds_alternative<TSetCookieSyntaxErrors>(parseRes)) {
                const auto errs = std::get<TSetCookieSyntaxErrors>(parseRes);
                errors.emplace_back(TSetCookieParserError{
                    .Info=std::string(TStringBuf("(") + ObfuscateSetCookieValue(rawSetCookie) + TStringBuf(")")),
                    .Reasons=errs,
                    .Action=ECookieAction::Drop,
                });
                stats.ParserCookieFail.Add(1);
                stats.IncParserErrors(errs);
                if (dryRun) {
                    stats.ParserCookieDropDryRun.Add(1);
                } else {
                    stats.ParserCookieDrop.Add(1);
                    continue;
                }
            } else {
                stats.ParserCookiePass.Add(1);
            }
            parsedHeaders.emplace_back(i, std::move(parseRes));
        }

        if (errors) {
            if (dryRun) {
                stats.ParserFixDryRun.Add(1);
            } else {
                stats.ParserFix.Add(1);
            }
            stats.ParserFail.Add(1);
        } else {
            stats.ParserPass.Add(1);
        }

        return std::make_tuple(std::move(parsedHeaders), std::move(errors));
    }

    TVector<TStringStorage> RenderResponseCookies(const TVector<TResponseCookie>& cookies, TArrayRef<TStringStorage> prevHeaders) noexcept {
        TVector<TStringStorage> res(Reserve(cookies.size()));
        for (auto& h : cookies) {
            if (h.SourceIdx()) {
                res.emplace_back(std::move(prevHeaders[*h.SourceIdx()]));
            } else if (std::holds_alternative<TSetCookie>(h.Cookie())) {
                auto blob = std::get<TSetCookie>(h.Cookie()).Render();
                res.emplace_back(TString((const char*)blob.Data(), blob.Size()));
            }
        }
        return res;
    }
}
