#pragma once

#include "cookie_headers.h"
#include "domain_info.h"
#include "gdpr.h"
#include "enums.h"
#include "policy_errors.h"
#include "stats.h"

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

#include <balancer/kernel/cookie/gdpr/cookie_type.h>
#include <balancer/kernel/cookie/gdpr/is_gdpr_b.h>
#include <balancer/kernel/cookie/set_cookie.h>
#include <balancer/kernel/http/parser/http.h>
#include <balancer/kernel/module/module_face.h>
#include <balancer/kernel/module/conn_descr.h>
#include <balancer/kernel/regexp/regexp_pire.h>

#include <geobase/include/lookup.hpp>

#include <util/generic/overloaded.h>

namespace NModCookiePolicy {
    using namespace NSrvKernel;
    using namespace NGdprCookie;


    struct TRequestArgs {
        const TConnDescr& Descr;
        const TDomainInfo& DomainInfo;
        const TExtractedIsGdpr& IsGdprCookie;
        const TMaybe<EIsGdpr>& IsGdprUpdate;
        const TExtractedIsGdprB& IsGdprBCookie;
        const TMaybe<TIsGdprB>& IsGdprBUpdate;
        const TCookieClassifier& CookieClassifier;
        TVector<TRequestCookie>& Cookies;
    };


    struct TResponseArgs {
        const TConnDescr& Descr;
        const TDomainInfo& DomainInfo;
        const TExtractedIsGdpr& IsGdprCookie;
        const TMaybe<EIsGdpr>& IsGdprUpdate;
        const TExtractedIsGdprB& IsGdprBCookie;
        const TMaybe<TIsGdprB>& IsGdprBUpdate;
        const TCookieClassifier& CookieClassifier;
        const ui32 DeletionSoftMax = 0;
        const ui32 DeletionHardMax = 0;
        TResponse& Resp;
        TVector<TResponseCookie>& Cookies;
    };


    class IPolicyCustomTls : TNonCopyable {
    public:
        virtual ~IPolicyCustomTls() = default;

        virtual THolder<IPolicyCustomTls> Clone(IWorkerCtl& ctl) const noexcept = 0;
    };


    class IPolicyCustomCtx : TNonCopyable  {
    public:
        virtual ~IPolicyCustomCtx() = default;
    };


    struct TPolicyArgs {
        const TConnDescr& Descr;
        const TDomainInfo& DomainInfo;
        const TExtractedIsGdpr& IsGdprCookie;
        const TMaybe<EIsGdpr>& IsGdprUpdate;
        const TExtractedIsGdprB& IsGdprBCookie;
        const TMaybe<TIsGdprB>& IsGdprBUpdate;
        const TCookieClassifier& CookieClassifier;
        const ui32 DeletionSoftMax = 0;
        const ui32 DeletionHardMax = 0;
        IPolicyCustomTls* const Tls = nullptr;
        IPolicyCustomCtx* const Ctx = nullptr;
        const bool DryRun = true;
    };


    using TSetCookiesCallback = std::function<void(const TVector<TSetCookie>&, TResponseCookieReasons)>;


    class ICookiePolicy {
    public:
        struct TAction {
            TCookieErrors<EResponseCookieReason> Reasons;
            ECookieAction Action = ECookieAction::Fix;
        };

        explicit ICookiePolicy(TStringBuf name, EPolicyMode mode)
            : Name_(name)
            , Mode_(mode)
        {}

        virtual ~ICookiePolicy() = default;

        const TString& Name() const noexcept {
            return Name_;
        }

        EPolicyMode Mode() const noexcept {
            return Mode_;
        }

        virtual THolder<IPolicyCustomTls> NewTls(TString /*uuid*/, TSharedStatsManager&) const noexcept {
            return {};
        }

        virtual THolder<IPolicyCustomCtx> NewCtx() const noexcept {
            return {};
        }

        virtual bool MatchRequest(const TPolicyArgs&) const noexcept {
            return true;
        }

        virtual TRequestCookieActions ApplyToRequestCookies(
            TVector<TRequestCookie>&, const TPolicyArgs&) const noexcept
        {
            return {};
        }

        virtual bool MatchResponseCookie(const TSetCookie&, const TPolicyArgs&) const noexcept {
            return false;
        }

        virtual TMaybe<TAction> ApplyToResponseCookie(TResponseCookieView, const TPolicyArgs&) const noexcept {
            return Nothing();
        }

        virtual void AddSetCookies(TSetCookiesCallback, const TPolicyArgs&) const noexcept {}

    private:
        const TString Name_;
        const EPolicyMode Mode_;
    };


    class TPolicyHolder;

    struct TPolicyTls : TNonCopyable {
        const TPolicyHolder& Policy;
        THolder<IPolicyCustomTls> CustomTls;
        TPolicyStats Stats;
        TPolicyCookieStats CookieStats;

    public:
        TPolicyTls(const TPolicyHolder& policy, TSharedStatsManager& manager);
        TPolicyTls(const TPolicyTls& tmpl, IWorkerCtl& ctl);
    };


    struct TPolicyCtx : TNonCopyable {
        TPolicyTls& Tls;
        const THolder<IPolicyCustomCtx> CustomCtx;
        const bool DryRun;
        bool Passed = true;

    public:
        TPolicyCtx(TPolicyTls& tls, bool dryRun) noexcept;
    };


    class TPolicyHolder : TNonCopyable {
    public:
        TPolicyHolder(TString moduleUuid, THolder<ICookiePolicy> policy);

        TStringBuf Uuid() const noexcept {
            return Uuid_;
        }

        [[nodiscard]]
        EPolicyMode PolicyMode() const noexcept {
            return Policy_->Mode();
        }

        [[nodiscard]]
        bool MatchRequest(const TPolicyTls& tls, const TRequestArgs& args) const noexcept;

        TRequestCookieActions ApplyToRequestCookies(TPolicyCtx& ctx, const TRequestArgs& args) const noexcept;
        TResponseCookieActions ApplyToResponseCookies(TPolicyCtx& ctx, const TResponseArgs& args) const noexcept;

        const ICookiePolicy& Policy() const noexcept {
            return *Policy_;
        }

    private:
        const TString Uuid_;
        const THolder<ICookiePolicy> Policy_;
    };


    class TNameMatchPolicyBase : public ICookiePolicy {
    protected:
        template <class TCfg>
        explicit TNameMatchPolicyBase(TString name, const TCfg& cfg, bool matchDeletion)
            : TNameMatchPolicyBase(name, cfg.mode(), cfg.name_re(), matchDeletion)
        {}

        explicit TNameMatchPolicyBase(TString name, EPolicyMode mode, TString nameRe, bool matchDeletion);

        bool MatchResponseCookie(const TSetCookie& c, const TPolicyArgs&) const noexcept override final;

    private:
        const TFsm NameRe_;
        const bool MatchDeletion_;
    };
}
