<?php
/**
 * 广告来源限制 — 4 级优先级检测 + 广告链接 query 拼装
 */
class CloakAdSourceGuard
{
    const PLATFORM_FB     = 'fb';
    const PLATFORM_GOOGLE = 'google';
    const PLATFORM_TIKTOK = 'tiktok';

    private static $refererDomains = [
        'fb.com',
        'facebook.com',
        'instagram.com',
        'l.facebook.com',
        'lm.facebook.com',
        'm.facebook.com',
        'google.com',
        'googleadservices.com',
        'doubleclick.net',
        'youtube.com',
        'googlesyndication.com',
        'tiktok.com',
        'tiktokv.com',
        'bytedance.com',
        'snssdk.com',
        'pangleglobal.com',
        'ads.tiktok.com',
    ];

    private static $uaPatterns = [
        'fban',
        'fbav',
        'instagram',
        'googleads',
        'adsbot-google',
        'gsa/',
        'tiktok',
        'musical_ly',
        'bytedancewebview',
        'bytelocale',
        'trill_',
    ];

    private static $clidParams = ['fbclid', 'gclid', 'wbraid', 'gbraid', 'ttclid'];

    private static $tiktokMacros = [
        '__CAMPAIGN_ID__',
        '__AID__',
        '__CID__',
        '__PLACEMENT__',
    ];

    private static $fullTemplates = [
        self::PLATFORM_FB => 'utm_source=facebook&site_source={{site_source_name}}&placement={{placement}}',
        self::PLATFORM_GOOGLE => 'utm_source=google&utm_term={keyword}&network={network}&placement={placement}',
        self::PLATFORM_TIKTOK => 'utm_source=tiktok&placement=__PLACEMENT__',
    ];

    private static $multiPlatformParams = [
        self::PLATFORM_FB     => 'campaign_id={{campaign.id}}',
        self::PLATFORM_GOOGLE => 'utm_campaign={campaignid}',
        self::PLATFORM_TIKTOK => 'adgroup_id=__CID__',
    ];

    /**
     * @param array $config  ad_fb, ad_google, ad_tiktok, ad_strict, ad_spm_id (bool/string)
     * @param array $context referer, query, user_agent
     * @return array { passed: bool, matched_by: string, reason: string }
     */
    public static function evaluate(array $config, array $context)
    {
        $enabled = self::anyPlatformEnabled($config);
        if (!$enabled) {
            return ['passed' => true, 'matched_by' => 'guard_disabled', 'reason' => ''];
        }

        $query     = $context['query'] ?? [];
        $referer   = strtolower((string) ($context['referer'] ?? ''));
        $userAgent = strtolower((string) ($context['user_agent'] ?? ''));
        $spmId     = trim((string) ($config['ad_spm_id'] ?? ''));

        // 优先级 1：ad_spm_id 强验证
        if ($spmId !== '' && isset($query['ad_spm_id']) && (string) $query['ad_spm_id'] === $spmId) {
            return ['passed' => true, 'matched_by' => 'ad_spm_id', 'reason' => ''];
        }

        // 优先级 2：clid 或 TikTok 动态宏
        foreach (self::$clidParams as $param) {
            if (!empty($query[$param])) {
                return ['passed' => true, 'matched_by' => 'clid:' . $param, 'reason' => ''];
            }
        }
        $queryString = self::buildQueryString($query);
        foreach (self::$tiktokMacros as $macro) {
            if (strpos($queryString, $macro) !== false) {
                return ['passed' => true, 'matched_by' => 'tiktok_macro:' . $macro, 'reason' => ''];
            }
        }

        // 优先级 3：Referer
        foreach (self::$refererDomains as $domain) {
            if ($referer !== '' && strpos($referer, $domain) !== false) {
                return ['passed' => true, 'matched_by' => 'referer:' . $domain, 'reason' => ''];
            }
        }

        // 优先级 4：User-Agent
        foreach (self::$uaPatterns as $pattern) {
            if ($userAgent !== '' && strpos($userAgent, $pattern) !== false) {
                return ['passed' => true, 'matched_by' => 'ua:' . $pattern, 'reason' => ''];
            }
        }

        $strict = !empty($config['ad_strict']);
        if ($strict) {
            return [
                'passed'     => false,
                'matched_by' => 'none',
                'reason'     => '来源限制:未命中广告来源验证',
            ];
        }

        return [
            'passed'     => false,
            'matched_by' => 'none',
            'reason'     => '未确定流量来源',
        ];
    }

    /**
     * @param array  $enabledPlatforms  PLATFORM_* 常量列表
     * @param string $spmId
     * @return string query 字符串（不含前导 ?）
     */
    public static function buildAdLinkQuery(array $enabledPlatforms, $spmId)
    {
        $spmId = trim((string) $spmId);
        if (empty($enabledPlatforms)) {
            return $spmId !== '' ? 'ad_spm_id=' . rawurlencode($spmId) : '';
        }

        $parts = [];
        if (count($enabledPlatforms) === 1) {
            $platform = $enabledPlatforms[0];
            if (isset(self::$fullTemplates[$platform])) {
                $parts[] = self::$fullTemplates[$platform];
            }
        } else {
            foreach ($enabledPlatforms as $platform) {
                if (isset(self::$multiPlatformParams[$platform])) {
                    $parts[] = self::$multiPlatformParams[$platform];
                }
            }
        }

        if ($spmId !== '') {
            $parts[] = 'ad_spm_id=' . rawurlencode($spmId);
        }

        return implode('&', $parts);
    }

    /**
     * 生成 10–12 位随机字母数字 ad_spm_id
     */
    public static function generateSpmId()
    {
        $len = random_int(10, 12);
        $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        $max   = strlen($chars) - 1;
        $id    = '';
        for ($i = 0; $i < $len; $i++) {
            $id .= $chars[random_int(0, $max)];
        }
        return $id;
    }

    /**
     * @param string $spmId
     */
    public static function isValidSpmId($spmId)
    {
        return (bool) preg_match('/^[A-Za-z0-9]{10,12}$/', (string) $spmId);
    }

    /**
     * @param array $config
     */
    public static function anyPlatformEnabled(array $config)
    {
        return !empty($config['ad_fb'])
            || !empty($config['ad_google'])
            || !empty($config['ad_tiktok']);
    }

    /**
     * 从当前站点配置读取广告来源限制参数
     */
    public static function configFromDefines()
    {
        return [
            'ad_fb'     => defined('CLOAK_AD_FB')     && strtoupper(CLOAK_AD_FB)     === 'ON',
            'ad_google' => defined('CLOAK_AD_GOOGLE') && strtoupper(CLOAK_AD_GOOGLE) === 'ON',
            'ad_tiktok' => defined('CLOAK_AD_TIKTOK') && strtoupper(CLOAK_AD_TIKTOK) === 'ON',
            'ad_strict' => defined('CLOAK_AD_STRICT') && strtoupper(CLOAK_AD_STRICT) === 'ON',
            'ad_spm_id' => defined('CLOAK_AD_SPM_ID') ? (string) CLOAK_AD_SPM_ID : '',
        ];
    }

    /**
     * 合并 Referer query 与 $_GET（与流水线 Guard 一致）
     */
    public static function buildMergedRequestQuery(array $get = null)
    {
        if ($get === null) {
            $get = $_GET;
        }
        $refRaw   = isset($_SERVER['HTTP_REFERER']) ? (string) $_SERVER['HTTP_REFERER'] : '';
        $refParts = parse_url($refRaw);
        $refQ     = [];
        if (!empty($refParts['query'])) {
            parse_str($refParts['query'], $refQ);
        }
        return array_merge($refQ, $get);
    }

    /**
     * 构建 evaluate 用的请求上下文
     */
    public static function buildRequestContext(array $get = null)
    {
        return [
            'referer'    => isset($_SERVER['HTTP_REFERER']) ? (string) $_SERVER['HTTP_REFERER'] : '',
            'query'      => self::buildMergedRequestQuery($get),
            'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? (string) $_SERVER['HTTP_USER_AGENT'] : '',
        ];
    }

    /**
     * 临时链接参数：是否满足开始/续期计时的来源条件
     * 已开启广告来源限制 → 走 4 级优先级规则；未开启 → 兼容旧 clid/ad_spm_id 触发
     */
    public static function shouldStartUrlArgsTimer(array $config, array $context)
    {
        if (!self::anyPlatformEnabled($config)) {
            $query = $context['query'] ?? [];
            foreach (array_merge(self::$clidParams, ['ad_spm_id']) as $param) {
                if (!empty($query[$param])) {
                    return true;
                }
            }
            return false;
        }

        $result = self::evaluate($config, $context);
        return !empty($result['passed']);
    }

    /**
     * @param array $query
     */
    private static function buildQueryString(array $query)
    {
        if (empty($query)) {
            return '';
        }
        return http_build_query($query);
    }
}
