#include "p_yandexuid_value.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 {
        auto ReadLegacyBannedYandexuids() {
            auto content = NResource::Find("/yandex_cookie_eraser.txt");
            THashSet<TString> res;
            TStringBuf iter(content);
            while (iter) {
                if (auto s = StripString(iter.NextTok('\n'))) {
                    res.emplace(s);
                }
            }
            return res;
        }

        constexpr TStringBuf Yandexuid = "yandexuid";

        struct TYandexuidValueCtx : public IPolicyCustomCtx {
            TVector<TString> Values;
        };
    }

    TPolicyYandexuidValue::TPolicyYandexuidValue(TString name, const TPolicyYandexuidValueCfg& cfg)
        : TNameMatchPolicyBase(name, cfg.mode(), ToString(Yandexuid), true)
        , Cfg_(cfg)
        // https://a.yandex-team.ru/arc/trunk/arcadia/crypta/lib/native/identifiers/lib/id_types/yandexuid.h?rev=5980160#L7
        , ValueRe_("[0]*[1-9][0-9]{16,19}")
        // https://wiki.yandex-team.ru/Cookies/yandexuid/#format
        , OldValueRe_("[1-9][0-9]{16,18}")
        , IgnoredOldValues_(ReadLegacyBannedYandexuids())
    {}

    THolder<IPolicyCustomCtx> TPolicyYandexuidValue::NewCtx() const noexcept {
        return MakeHolder<TYandexuidValueCtx>();
    }

    TRequestCookieActions TPolicyYandexuidValue::ApplyToRequestCookies(
        TVector<TRequestCookie>& cc, const TPolicyArgs& args) const noexcept
    {
        if (Cfg_.allow_change()) {
            return {};
        }

        TYandexuidValueCtx& ctx = (TYandexuidValueCtx&)*args.Ctx;
        for (auto&& c : cc) {
            // yandexuid except the one we added by ourselves. Allows backends to override our generated yandexuid
            // See yandexuid_create policy.
            if (c.NameValue.Name == Yandexuid && !c.Added) {
                ctx.Values.emplace_back(c.NameValue.Value);
            }
        }

        return {};
    }

    TMaybe<ICookiePolicy::TAction> TPolicyYandexuidValue::ApplyToResponseCookie(
        TResponseCookieView c, const TPolicyArgs& args) const noexcept
    {
        TYandexuidValueCtx& ctx = (TYandexuidValueCtx&)*args.Ctx;
        const auto& cc = c.Const();

        if (HasDeletion(cc, args.Descr.Properties->Start)) {
            if (!Cfg_.allow_change()) {
                return TAction{
                    .Reasons={EResponseCookieReason::Deletion},
                    .Action=ECookieAction::Drop,
                };
            }
            return Nothing();
        }

        if (ctx.Values) {
            bool invalidOld = false;
            for (auto&& v : ctx.Values) {
                if (v == NCookie::ToStringBuf(cc.Value)) {
                    // Found a matching value, no further checks needed
                    return Nothing();
                }

                if (!Match(OldValueRe_, v) || IgnoredOldValues_.contains(v)) {
                    invalidOld = true;
                    break;
                }
            }

            if (!invalidOld && !Cfg_.allow_change()) {
                return TAction{
                    .Reasons={EResponseCookieReason::ValueChange},
                    .Action=ECookieAction::Drop,
                };
            }
        }

        // Ok with change if the new value fits
        if (!Match(ValueRe_, NCookie::ToStringBuf(cc.Value))) {
            return TAction{
                .Reasons={EResponseCookieReason::BadValue},
                .Action=ECookieAction::Drop,
            };
        }

        return Nothing();
    }
}
