<?php
require_once __DIR__ . '/ClientIpResolver.php';
require_once __DIR__ . '/GeoIpCountryCache.php';
require_once __DIR__ . '/GeoIpCountryResolver.php';
require_once __DIR__ . '/FpUrlHelper.php';
require_once __DIR__ . '/RiskSecondaryGate.php';

/**
 * 客户端 Session（PHPSESSID）统一判定缓存：byApi 阶段缓存 + byApiRisk 最终缓存。
 */
class CloakJudgmentCache
{
    const SESSION_KEY = 'cloak_judgment_cache';
    const STAGE_BYAPI = 'byapi';
    const STAGE_FINAL = 'final';

    public static function hasSession()
    {
        return !empty($_SESSION[self::SESSION_KEY])
            && is_array($_SESSION[self::SESSION_KEY]);
    }

    /**
     * @param string|null $currentIp
     * @return array|null
     */
    public static function get($currentIp = null)
    {
        if (!self::hasSession()) {
            return null;
        }

        $cached = $_SESSION[self::SESSION_KEY];
        if (!self::isConfigValid($cached)) {
            self::clear();
            $_SESSION['visit_to_3'] = '1';
            return null;
        }

        $ip = ClientIpResolver::normalizeIp(
            $currentIp !== null && $currentIp !== '' ? $currentIp : ClientIpResolver::resolve()
        );
        $cachedIp = ClientIpResolver::normalizeIp($cached['ip'] ?? '');

        if ($ip !== null && $cachedIp !== null && $ip !== $cachedIp) {
            self::refreshCountryForCurrentIp($ip);
            $cached = $_SESSION[self::SESSION_KEY];
        }

        return $cached;
    }

    /**
     * 二次风控关闭（CLOAK_RISK_NUMBER=0 等）时，byApi 阶段缓存即视为最终缓存。
     *
     * @param array $cached
     */
    public static function isEffectivelyFinal(array $cached)
    {
        $stage = (string) ($cached['stage'] ?? '');
        if ($stage === self::STAGE_FINAL) {
            return true;
        }
        if ($stage === self::STAGE_BYAPI && !RiskSecondaryGate::isEnabled()) {
            return true;
        }
        return false;
    }

    /**
     * 仅返回最终缓存（可短路全流程）。
     *
     * @param string|null $currentIp
     * @return array|null
     */
    public static function getFinal($currentIp = null)
    {
        $cached = self::get($currentIp);
        if ($cached === null || !self::isEffectivelyFinal($cached)) {
            return null;
        }
        return $cached;
    }

    /**
     * @param array|null $cached
     */
    public static function isFinal($cached = null)
    {
        if ($cached === null) {
            $cached = self::get();
        }
        return is_array($cached) && self::isEffectivelyFinal($cached);
    }

    /**
     * @param array|null $cached
     */
    public static function isPartial($cached = null)
    {
        if ($cached === null) {
            $cached = self::get();
        }
        if (!is_array($cached) || ($cached['stage'] ?? '') !== self::STAGE_BYAPI) {
            return false;
        }
        return RiskSecondaryGate::isEnabled();
    }

    /**
     * byApi 完成后写入；按风险系数决定 stage。
     * CLOAK_RISK_NUMBER=0（或未启用二次风控）时，byApi 结果直接记为 final。
     *
     * @param array{result:string,reason?:string,ip?:string,country?:string,fp_url?:string} $payload
     */
    public static function storeFromByApi(array $payload)
    {
        $result = ($payload['result'] ?? '') === 'true' ? 'true' : 'false';
        $stage  = self::STAGE_FINAL;
        if ($result === 'true' && RiskSecondaryGate::isEnabled()) {
            $stage = self::STAGE_BYAPI;
        }
        self::writeCache($stage, $payload);
        self::applyToSession($_SESSION[self::SESSION_KEY]);
    }

    /**
     * byApiRisk 完成后写入最终缓存。
     *
     * @param array{result:string,reason?:string,fp_url?:string,ip?:string,country?:string} $payload
     */
    public static function storeFromByApiRisk(array $payload)
    {
        self::writeCache(self::STAGE_FINAL, $payload);
        self::applyToSession($_SESSION[self::SESSION_KEY]);
    }

    /**
     * @param array $cached
     */
    public static function applyToSession(array $cached)
    {
        $result = ($cached['result'] ?? '') === 'true' ? 'true' : 'false';
        $_SESSION['check_result'] = $result;
        $_SESSION['visit_to_2']  = '2';

        if (self::isEffectivelyFinal($cached)) {
            $_SESSION['visit_to_3'] = '3';
        } else {
            $_SESSION['visit_to_3'] = '1';
        }

        if (!empty($cached['ip'])) {
            $_SESSION['gcu_ip'] = $cached['ip'];
        }
        if (array_key_exists('country', $cached)) {
            $_SESSION['gcu_country'] = (string) $cached['country'];
        }
    }

    /**
     * @param object $v_info visitorInfo
     * @param array  $cached
     */
    public static function applyToVisitorInfo($v_info, array $cached)
    {
        if (!is_object($v_info)) {
            return;
        }
        if (!empty($cached['ip'])) {
            $v_info->v_ip = $cached['ip'];
        }
        if (array_key_exists('country', $cached) && (string) $cached['country'] !== '') {
            $v_info->v_country = (string) $cached['country'];
        }
    }

    public static function clear()
    {
        unset($_SESSION[self::SESSION_KEY]);
    }

    public static function hitReason()
    {
        return '缓存';
    }

    /**
     * @param array $cached
     * @return array{result:bool,reason:string,link:string}
     */
    public static function toApiResponse(array $cached)
    {
        $passed = ($cached['result'] ?? '') === 'true';
        $link   = (string) ($cached['fp_url'] ?? '');

        if ($passed && $link === '') {
            $link = FpUrlHelper::fallbackUrl();
        }
        if (!$passed && $link === '' && defined('DB_ZP')) {
            $link = (string) DB_ZP;
        }

        return [
            'result' => $passed,
            'reason' => self::hitReason(),
            'link'   => $link,
        ];
    }

    /**
     * @param string $stage
     * @param array  $payload
     */
    private static function writeCache($stage, array $payload)
    {
        $ip = ClientIpResolver::normalizeIp($payload['ip'] ?? ClientIpResolver::resolve());
        if ($ip === null) {
            return;
        }

        $country = (string) ($payload['country'] ?? '');
        if ($country === '' && !empty($_SESSION['gcu_country'])) {
            $country = (string) $_SESSION['gcu_country'];
        }
        if ($country === '') {
            $country = (string) (GeoIpCountryResolver::resolve($ip) ?? '');
        }

        $_SESSION[self::SESSION_KEY] = [
            'stage'       => $stage,
            'ip'          => $ip,
            'country'     => $country,
            'result'      => ($payload['result'] ?? '') === 'true' ? 'true' : 'false',
            'reason'      => (string) ($payload['reason'] ?? ''),
            'fp_url'      => (string) ($payload['fp_url'] ?? ''),
            'risk_number' => defined('CLOAK_RISK_NUMBER') ? (int) CLOAK_RISK_NUMBER : 0,
            'campaign_id' => defined('COSTM_IP_SCORE') ? (string) COSTM_IP_SCORE : '',
            'cached_at'   => time(),
        ];
    }

    /**
     * @param string $currentIp
     */
    private static function refreshCountryForCurrentIp($currentIp)
    {
        if (!self::hasSession()) {
            return;
        }
        $cached            = $_SESSION[self::SESSION_KEY];
        $cached['ip']      = $currentIp;
        $cached['country'] = (string) (GeoIpCountryResolver::resolve($currentIp) ?? '');
        $_SESSION[self::SESSION_KEY] = $cached;
        $_SESSION['gcu_ip']          = $currentIp;
        $_SESSION['gcu_country']     = $cached['country'];
    }

    /**
     * @param array $cached
     */
    private static function isConfigValid(array $cached)
    {
        if (defined('CLOAK_RISK_NUMBER') && (int) ($cached['risk_number'] ?? -1) !== (int) CLOAK_RISK_NUMBER) {
            return false;
        }
        if (defined('COSTM_IP_SCORE') && (string) ($cached['campaign_id'] ?? '') !== (string) COSTM_IP_SCORE) {
            return false;
        }
        return true;
    }
}
