#include "p_gdpr_client_cookie.h"

#include <balancer/modules/cookie_policy/common/controls_reader.h>
#include <balancer/modules/cookie_policy/common/utils.h>

#include <balancer/kernel/cookie/deletion/delete_cookie.h>

#include <util/generic/vector.h>
#include <util/random/shuffle.h>

#include <string>

namespace NModCookiePolicy {

    namespace {
        struct TPolicyGdprClientCookieCtx : public IPolicyCustomCtx {
            TVector<std::string> NamesToDelete;
        };
    }


    TPolicyGdprClientCookie::TPolicyGdprClientCookie(TString name, const TPolicyGdprClientCookieCfg& cfg)
        : ICookiePolicy(name, cfg.mode())
        , Cfg_(cfg)
    {}

    THolder<IPolicyCustomCtx> TPolicyGdprClientCookie::NewCtx() const noexcept {
        return MakeHolder<TPolicyGdprClientCookieCtx>();
    }

    bool TPolicyGdprClientCookie::MatchRequest(const TPolicyArgs& args) const noexcept {
        return args.DomainInfo.IsSecure
            && args.Descr.Properties->Gdpr.SafeValue.IsGdpr
            && args.Descr.Properties->Gdpr.SafeValue.Mode != EGdpr::KeepAll
            && args.DomainInfo.DomainRoot;
    }

    TRequestCookieActions TPolicyGdprClientCookie::ApplyToRequestCookies(
        TVector<TRequestCookie>& cookies, const TPolicyArgs& args) const noexcept
    {
        TPolicyGdprClientCookieCtx& ctx = *(TPolicyGdprClientCookieCtx*)args.Ctx;
        const auto mode = args.Descr.Properties->Gdpr.SafeValue.Mode;

        for (auto&& c : cookies) {
            if (!NGdprCookie::KeepCookie(args.CookieClassifier.GetCookieType(c.NameValue.Name.GetOrElse({})), mode)) {
                ctx.NamesToDelete.emplace_back(c.NameValue.Name.GetOrElse({}));
            }
        }

        if (ctx.NamesToDelete.empty()) {
            return {};
        }

        Sort(ctx.NamesToDelete);
        Shuffle(ctx.NamesToDelete.begin(), ctx.NamesToDelete.end());

        return {};
    }

    void TPolicyGdprClientCookie::AddSetCookies(TSetCookiesCallback cb, const TPolicyArgs& args) const noexcept {
        const TPolicyGdprClientCookieCtx& ctx = *(TPolicyGdprClientCookieCtx*)args.Ctx;

        if (!ctx.NamesToDelete) {
            return;
        }

        TResponseCookieReasons res = {EResponseCookieReason::GdprDelete};
        TVector<TSetCookie> deletions(Reserve(args.DeletionHardMax));

        for (auto&& name : ctx.NamesToDelete) {
            if (deletions.size() < args.DeletionSoftMax) {
                // always try to delete a cookie in the most robust way
                DeleteCookieEveryDomainPath(deletions, {
                    .Name=name,
                    .Path=args.Descr.Request->RequestLine().Path.AsStringBuf(),
                    .Host=TMaybe<TStringBuf>(args.DomainInfo.Domain).GetOrElse({}),
                    .Root=TMaybe<TStringBuf>(args.DomainInfo.DomainRoot),
                    .Secure=true,
                });
            }

            // SoftMax is guaranteed not to exceed HardMax
            if (deletions.size() >= args.DeletionSoftMax) {
                if (TruncateDeletions(deletions, args.DeletionHardMax)) {
                    res.Set(EResponseCookieReason::HardLimited);
                }
                res.Set(EResponseCookieReason::SoftLimited);
                break;
            }
        }

        cb(deletions, res);
    }
}
