#pragma once

#include "p_gdpr_client_cookie.h"
#include "p_gdpr_lifetime.h"
#include "p_gdpr_server_cookie.h"
#include "p_global_scope.h"
#include "p_http_only_false.h"
#include "p_is_gdpr_cookie.h"
#include "p_persistent_lifetime.h"
#include "p_protected_cookie.h"
#include "p_samesite_none.h"
#include "p_secure_true.h"
#include "p_yandexuid_create.h"
#include "p_yandexuid_value.h"

#include <balancer/modules/cookie_policy/common/combined_policy.h>
#include <balancer/modules/cookie_policy/common/cookie_policy.cfgproto.pb.h>

namespace NModCookiePolicy {

    template <class TPolCfg>
    struct TPolicyTraits;

#define Y_COOKIE_POLICY_TRAITS(policy, impl)    \
    class impl;                                 \
    template <>                                 \
    struct TPolicyTraits<impl ## Cfg> {         \
        using TConf = impl ## Cfg;              \
        using TImpl = impl;                     \
        template <class TModCfg>                \
        static auto& Get(TModCfg& cfg) noexcept { return cfg.policy(); }    \
    }

    Y_COOKIE_POLICY_TRAITS(is_gdpr_cookie, TPolicyIsGdprCookie);
    Y_COOKIE_POLICY_TRAITS(is_gdpr_b_cookie, TPolicyIsGdprBCookie);
    Y_COOKIE_POLICY_TRAITS(gdpr_client_cookie, TPolicyGdprClientCookie);
    Y_COOKIE_POLICY_TRAITS(gdpr_lifetime, TPolicyGdprLifetime);
    Y_COOKIE_POLICY_TRAITS(gdpr_server_cookie, TPolicyGdprServerCookie);
    Y_COOKIE_POLICY_TRAITS(protected_cookie, TPolicyProtectedCookie);
    Y_COOKIE_POLICY_TRAITS(yandexuid_value, TPolicyYandexuidValue);
    Y_COOKIE_POLICY_TRAITS(yandexuid_create, TPolicyYandexuidCreate);
    Y_COOKIE_POLICY_TRAITS(persistent_lifetime, TPolicyPersistentLifetime);
    Y_COOKIE_POLICY_TRAITS(global_scope, TPolicyGlobalScope);
    Y_COOKIE_POLICY_TRAITS(http_only_false, TPolicyHttpOnlyFalse);
    Y_COOKIE_POLICY_TRAITS(secure_true, TPolicySecureTrue);
    Y_COOKIE_POLICY_TRAITS(samesite_none, TPolicySameSiteNone);

    template <class TPolCfg>
    inline auto MakePolicy(TString name, TPolCfg&& polCfg) {
        return MakeHolder<typename TPolicyTraits<std::decay_t<TPolCfg>>::TImpl>(name, polCfg);
    }

    template <class TPolCfg, class TModCfg>
    inline auto& GetPoliciesMap(TModCfg&& cfg) {
        return TPolicyTraits<std::decay_t<TPolCfg>>::Get(cfg);
    }

    template <class TPolCfg>
    inline void AppendPolicies(TDeque<TPolicyHolder>& res, THashSet<TString>& uniqNames, const TModuleConfig& cfg) {
        auto&& policies = GetPoliciesMap<TPolCfg>(cfg);
        TVector<TString> names;
        for (auto&& pl : policies) {
            Y_ENSURE_EX(uniqNames.insert(pl.first).second,
                TConfigParseError() << "A non-unique policy name: " << pl.first);
            names.emplace_back(pl.first);
        }
        Sort(names);
        for (auto&& n : names) {
            auto polCfg = policies.find(n)->second;
            res.emplace_back(cfg.uuid(), MakePolicy(n, polCfg));
        }
    };

    TCombinedPolicy InitCombinedPolicy(const TModuleConfig& cfg);
}
