#include "p_yandexuid_create.h"

#include <balancer/modules/cookie_policy/common/utils.h>
#include <balancer/kernel/client_hints/impl/client_hints.h>
#include <balancer/kernel/cookie/gdpr/lifetime.h>
#include <balancer/kernel/helpers/yuid.h>

#include <library/cpp/resource/resource.h>

namespace NModCookiePolicy {
    namespace {
        constexpr TStringBuf Yandexuid = "yandexuid";

        struct TYandexuidCreateCtx : public IPolicyCustomCtx {
            TMaybe<TBlob> NewValue;
        };
    }

    TPolicyYandexuidCreate::TPolicyYandexuidCreate(TString name, const TPolicyYandexuidCreateCfg& cfg)
        : TNameMatchPolicyBase(name, cfg.mode(), ToString(Yandexuid), false)
    {}

    THolder<IPolicyCustomCtx> TPolicyYandexuidCreate::NewCtx() const noexcept {
        return MakeHolder<TYandexuidCreateCtx>();
    }

    bool TPolicyYandexuidCreate::MatchRequest(const TPolicyArgs& args) const noexcept {
        return !!args.DomainInfo.DomainRoot;
    }

    TRequestCookieActions TPolicyYandexuidCreate::ApplyToRequestCookies(
        TVector<TRequestCookie>& cc, const TPolicyArgs& args) const noexcept
    {
        TYandexuidCreateCtx& ctx = (TYandexuidCreateCtx&)*args.Ctx;
        for (auto&& c : cc) {
            if (c.NameValue.Name == Yandexuid) {
                // Already have a yandexuid
                return {};
            }
        }

        if (auto value = args.Descr.Request->Headers().GetFirstValue("X-Yandex-RandomUID")) {
            ctx.NewValue = TBlob::CopySingleThreaded(value.data(), value.size());
        }

        if (!ctx.NewValue) {
            ctx.NewValue = TBlob::FromStringSingleThreaded(
                GetYuid(args.Descr.Properties->Random, args.Descr.Properties->Start));
        }

        cc.emplace_back(TRequestCookie{
            .NameValue={.Name=Yandexuid, .Value=NCookie::ToStringBuf(*ctx.NewValue)},
            .Memory=*ctx.NewValue,
            .Added=true,
        });

        return {TRequestCookieAction{
            .Info=std::string(Yandexuid),
            .Reasons={ERequestCookieReason::Create},
            .Action=ECookieAction::Add,
        }};
    }

    TMaybe<ICookiePolicy::TAction> TPolicyYandexuidCreate::ApplyToResponseCookie(
        TResponseCookieView, const TPolicyArgs& args) const noexcept
    {
        TYandexuidCreateCtx& ctx = (TYandexuidCreateCtx&)*args.Ctx;
        // Have a set-cookie from server, cancelling
        ctx.NewValue.Clear();
        return Nothing();
    }

    void TPolicyYandexuidCreate::AddSetCookies(TSetCookiesCallback cb, const TPolicyArgs& args) const noexcept {
        TYandexuidCreateCtx& ctx = (TYandexuidCreateCtx&)*args.Ctx;
        if (!ctx.NewValue) {
            return;
        }

        constexpr auto y10 = TDuration::Days(365 * 10);
        const auto now = args.Descr.Properties->Start;
        const bool isGdpr = args.Descr.Properties->Gdpr.Value.IsGdpr;
        const auto sameSite = GetOrSetUATraits(args.Descr).SameSiteNoneSupport
            ? ESameSite::None
            : ESameSite::Undefined;
        const auto expires = isGdpr
            ? NGdprCookie::TMaxAllowedExpires::Get(now)
            : TExpiresGlobalCache<y10.Seconds()>::Get(now);

        cb({TSetCookie{
            .Name=std::string(Yandexuid),
            .Value=*ctx.NewValue,
            .Path=std::string("/"),
            .Domain=std::string(".") + *args.DomainInfo.DomainRoot,
            .Expires=expires,
            .SameSite=sameSite,
            .Secure=true,
            .HttpOnly=false,
        }}, {EResponseCookieReason::Create});
    }
}
