<?php

namespace App\Jobs;

use App\Services\IpRiskService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;

/**
 * IP风险检测异步任务
 *
 * 在用户注册或登录成功后异步执行，检测用户IP是否存在风险：
 * 1. 如果用户已在 Redis Hash 中有记录且 IP 未变 → 跳过
 * 2. IP 变更后重新检测
 * 3. 检测到风险 → 写入 Redis Hash: ip_risk_users → {UserID} → {可疑IP}
 * 4. 未检测到风险 → 从 Hash 中移除（如果之前存在）
 */
class IpRiskDetection implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /**
     * Redis Hash key
     */
    const REDIS_HASH_KEY = 'ip_risk_users';

    /**
     * 上次检测 IP 缓存 —— 按日分片 Hash，3 天自动过期
     * key: ip_risk_last_detected:20260507
     * field: UserID
     * value: IP
     * 每天一个 Hash，查过去 3 天的记录，无需大量独立 key
     */
    const LAST_DETECTED_PREFIX = 'ip_risk_last_detected:';

    /**
     * 每次检测覆盖的天数（含当天）
     */
    const DETECTION_WINDOW_DAYS = 3;

    /**
     * @var int 用户ID
     */
    protected $userId;

    /**
     * @var string 待检测的 IP 地址
     */
    protected $ip;

    /**
     * Create a new job instance.
     *
     * @param int    $userId
     * @param string $ip
     */
    public function __construct($userId, $ip)
    {
        $this->userId = $userId;
        $this->ip = $ip;
    }

    /**
     * 获取过去 N 天的日期 key 列表（当天在最前）
     *
     * @param int $days
     * @return array
     */
    private function getRecentDateKeys($days = 3)
    {
        $keys = [];
        for ($i = 0; $i < $days; $i++) {
            $keys[] = self::LAST_DETECTED_PREFIX . date('Ymd', strtotime("-{$i} days"));
        }
        return $keys;
    }

    /**
     * Execute the job.
     */
    public function handle()
    {
        $userId = $this->userId;
        $ip = $this->ip;

        if (empty($userId) || empty($ip)) {
            return;
        }

        // 1. 检查过去 3 天的每日 Hash 中是否已检测过同一 IP
        //    只要任意一天记录相同 IP → 跳过（IP 未变）
        $recentKeys = $this->getRecentDateKeys(self::DETECTION_WINDOW_DAYS);
        $todayKey = $recentKeys[0];

        $ipUnchanged = false;
        foreach ($recentKeys as $key) {
            $storedIp = Redis::hget($key, $userId);
            if ($storedIp === $ip) {
                $ipUnchanged = true;
                break;
            }
        }

        if ($ipUnchanged) {
            // IP 在检测窗口内未变，跳过
            return;
        }

        // 2. 执行风险检测
        $service = new IpRiskService();
        $result = $service->detect($ip);

        // 3. 根据检测结果更新风险标记 Hash
        if ($result['is_risky']) {
            $value = $ip . '|' . $result['reason'];
            Redis::hset(self::REDIS_HASH_KEY, $userId, $value);
            Log::info("IpRiskDetection: user flagged", [
                'user_id' => $userId,
                'ip'      => $ip,
                'reason'  => $result['reason'],
            ]);
        }

        // 4. 记录本次检测结果到今天的 Hash，并确保 3 天过期
        $isNew = !Redis::exists($todayKey);
        Redis::hset($todayKey, $userId, $ip);
        if ($isNew) {
            Redis::expire($todayKey, 86400 * self::DETECTION_WINDOW_DAYS);
        }
    }
}
