<?php

declare(strict_types=1);

namespace Kylin\Education\Services;

use Illuminate\Support\Facades\Log;

/**
 * 考试防作弊服务
 *
 * 依据《中华人民共和国教育法》第四十四条：
 * 受教育者应当履行遵守所在学校或者其他教育机构的管理制度的义务
 *
 * 依据 GB/T 45574-2025《敏感信息处理要求》：
 * 考试数据需加密传输
 *
 * 依据《未成年人保护法》：
 * 在线考试需考虑未成年人特殊保护
 *
 * 防作弊机制：
 *  - 切屏检测：超出限制自动交卷
 *  - 选项乱序：随机打乱选项顺序
 *  - 题目乱序：随机打乱题目顺序
 *  - 异常行为检测：快速答题、长时间无操作
 *
 * 使用方式：
 *   $result = AntiCheatService::checkSwitchViolation($attempt, $switchCount);
 *   $result = AntiCheatService::detectAnomalousBehavior($attempt, $behaviorData);
 *   $options = AntiCheatService::shuffleOptions($options);
 */
final class AntiCheatService
{
    /**
     * 异常行为类型
     */
    public const ANOMALY_FAST_ANSWER = 'fast_answer';       // 异常快速答题（< 3 秒）
    public const ANOMALY_IDLE_TIMEOUT = 'idle_timeout';     // 长时间无操作（> 5 分钟）
    public const ANOMALY_SWITCH_LIMIT = 'switch_limit';     // 切屏超限
    public const ANOMALY_MULTI_IP = 'multi_ip';             // 多 IP 答题
    public const ANOMALY_TIME_OVERLAP = 'time_overlap';     // 同时参加多场考试

    /**
     * 检查切屏违规
     *
     * 依据配置的 max_switch_count，超出限制自动交卷。
     *
     * @param array $attempt 答题记录
     * @param int $switchCount 当前切屏次数
     * @return array{violation: bool, action: string, message: string}
     */
    public static function checkSwitchViolation(array $attempt, int $switchCount): array
    {
        $maxSwitchCount = config('kylin-education.exam.max_switch_count', 3);
        $autoSubmit = config('kylin-education.exam.auto_submit_on_switch_limit', true);

        if ($switchCount > $maxSwitchCount) {
            return [
                'violation' => true,
                'action' => $autoSubmit ? 'auto_submit' : 'flag',
                'message' => "切屏次数（{$switchCount}）已超过限制（{$maxSwitchCount}），系统将自动交卷",
                'switch_count' => $switchCount,
                'max_allowed' => $maxSwitchCount,
                'law' => '《教育法》第四十四条：受教育者应当遵守考试纪律',
            ];
        }

        // 接近限制时的警告
        if ($switchCount >= $maxSwitchCount - 1) {
            return [
                'violation' => false,
                'action' => 'warning',
                'message' => "警告：切屏次数（{$switchCount}）即将达到限制（{$maxSwitchCount}），再次切屏将自动交卷",
                'switch_count' => $switchCount,
                'max_allowed' => $maxSwitchCount,
                'law' => '《教育法》第四十四条',
            ];
        }

        return [
            'violation' => false,
            'action' => 'none',
            'message' => '',
            'switch_count' => $switchCount,
            'max_allowed' => $maxSwitchCount,
        ];
    }

    /**
     * 检测异常答题行为
     *
     * @param array $attempt 答题记录
     * @param array $behaviorData 行为数据
     * @return array{has_anomaly: bool, anomalies: array}
     */
    public static function detectAnomalousBehavior(array $attempt, array $behaviorData): array
    {
        $anomalies = [];

        // 检测异常快速答题（< 3 秒）
        if (! empty($behaviorData['answer_time']) && $behaviorData['answer_time'] < 3) {
            $anomalies[] = [
                'type' => self::ANOMALY_FAST_ANSWER,
                'message' => "答题速度异常（{$behaviorData['answer_time']} 秒），疑似作弊",
                'threshold' => 3,
                'actual' => $behaviorData['answer_time'],
            ];
        }

        // 检测长时间无操作（> 5 分钟）
        if (! empty($behaviorData['idle_seconds']) && $behaviorData['idle_seconds'] > 300) {
            $anomalies[] = [
                'type' => self::ANOMALY_IDLE_TIMEOUT,
                'message' => "长时间无操作（{$behaviorData['idle_seconds']} 秒），请确认考生状态",
                'threshold' => 300,
                'actual' => $behaviorData['idle_seconds'],
            ];
        }

        // 检测多 IP 答题
        if (! empty($behaviorData['ips']) && is_array($behaviorData['ips']) && count($behaviorData['ips']) > 1) {
            $anomalies[] = [
                'type' => self::ANOMALY_MULTI_IP,
                'message' => '检测到多个 IP 地址答题，疑似代考',
                'ips' => $behaviorData['ips'],
            ];
        }

        return [
            'has_anomaly' => ! empty($anomalies),
            'anomalies' => $anomalies,
            'checked_at' => now()->toIso8601String(),
        ];
    }

    /**
     * 随机打乱选项顺序
     *
     * 依据 GB/T 45574-2025，考试数据需防作弊处理。
     *
     * @param array $options 选项数组 [['key' => 'A', 'value' => '...'], ...]
     * @return array 乱序后的选项
     */
    public static function shuffleOptions(array $options): array
    {
        if (! config('kylin-education.exam.randomize_options', true)) {
            return $options;
        }

        $shuffled = $options;
        shuffle($shuffled);

        // 重新分配选项字母
        $letters = range('A', 'Z');
        foreach ($shuffled as $index => &$option) {
            $option['key'] = $letters[$index] ?? chr(65 + $index);
        }

        return $shuffled;
    }

    /**
     * 随机打乱题目顺序
     *
     * @param array $questions 题目数组
     * @return array 乱序后的题目
     */
    public static function shuffleQuestions(array $questions): array
    {
        if (! config('kylin-education.exam.randomize_questions', true)) {
            return $questions;
        }

        $shuffled = $questions;
        shuffle($shuffled);

        return $shuffled;
    }

    /**
     * 验证考试时间窗口
     *
     * @param array $exam 考试数据（含 start_time, end_time, duration）
     * @return bool 是否在有效考试时间内
     */
    public static function validateTimeWindow(array $exam): bool
    {
        $now = now();

        // 检查考试开始时间
        if (! empty($exam['start_time']) && $now->lt($exam['start_time'])) {
            return false;
        }

        // 检查考试结束时间
        if (! empty($exam['end_time']) && $now->gt($exam['end_time'])) {
            return false;
        }

        return true;
    }

    /**
     * 检查考试并发人数
     *
     * @param int $examId 考试 ID
     * @return bool 是否超过并发限制
     */
    public static function checkConcurrency(int $examId): bool
    {
        $concurrentLimit = config('kylin-education.exam.concurrent_limit', 300);

        // 实际业务中应从 Redis 或数据库查询当前在线考试人数
        $currentCount = 0; // 由业务层实现

        return $currentCount < $concurrentLimit;
    }

    /**
     * 生成防作弊 Token
     *
     * 用于验证考试请求的合法性，防止直接调用 API 作弊。
     *
     * @param int $examId 考试 ID
     * @param int $studentId 学生 ID
     * @return string
     */
    public static function generateCheatToken(int $examId, int $studentId): string
    {
        $payload = implode('|', [$examId, $studentId, now()->timestamp]);
        $appKey = config('app.key');

        return hash_hmac('sha256', $payload, $appKey);
    }

    /**
     * 验证防作弊 Token
     *
     * @param string $token 防作弊 Token
     * @param int $examId 考试 ID
     * @param int $studentId 学生 ID
     * @param int $timestamp 时间戳
     * @return bool
     */
    public static function verifyCheatToken(string $token, int $examId, int $studentId, int $timestamp): bool
    {
        // Token 有效期 30 分钟
        if (abs(now()->timestamp - $timestamp) > 1800) {
            return false;
        }

        $expectedToken = self::generateCheatToken($examId, $studentId);
        return hash_equals($expectedToken, $token);
    }

    /**
     * 记录防作弊审计日志
     *
     * 依据《教育法》和《未成年人保护法》，
     * 考试作弊检测、违规处理等操作需记录审计日志。
     */
    private static function audit(string $event, int $examId, int $studentId, array $extra = []): void
    {
        if (! class_exists(\Kylin\AuditLog\Models\AuditLog::class)) {
            return;
        }

        try {
            \Kylin\AuditLog\Models\AuditLog::create([
                'event' => $event,
                'model_type' => 'exam_attempt',
                'model_id' => $examId,
                'new_values' => array_merge([
                    'exam_id' => $examId,
                    'student_id' => $studentId,
                ], $extra),
                'old_values' => null,
                'user_id' => auth()->id() ?? $studentId,
                'ip' => request()?->ip(),
                'url' => request()?->fullUrl(),
                'tag' => 'education-anti-cheat',
            ]);
        } catch (\Throwable $e) {
            Log::warning('防作弊审计日志写入失败', [
                'error' => $e->getMessage(),
                'exam_id' => $examId,
                'student_id' => $studentId,
                'event' => $event,
            ]);
        }
    }

    /**
     * 检查切屏违规并记录审计日志
     *
     * @param array $attempt 答题记录
     * @param int $switchCount 当前切屏次数
     * @return array
     */
    public static function checkSwitchViolationWithAudit(array $attempt, int $switchCount): array
    {
        $result = self::checkSwitchViolation($attempt, $switchCount);

        if ($result['violation']) {
            self::audit('switch_violation_detected', $attempt['exam_id'] ?? 0, $attempt['student_id'] ?? 0, [
                'switch_count' => $switchCount,
                'max_allowed' => $result['max_allowed'] ?? 0,
                'action' => $result['action'],
                'law' => $result['law'] ?? '',
            ]);
        }

        return $result;
    }

    /**
     * 检测异常行为并记录审计日志
     *
     * @param array $attempt 答题记录
     * @param array $behaviorData 行为数据
     * @return array
     */
    public static function detectAnomalousBehaviorWithAudit(array $attempt, array $behaviorData): array
    {
        $result = self::detectAnomalousBehavior($attempt, $behaviorData);

        if ($result['has_anomaly']) {
            self::audit('anomalous_behavior_detected', $attempt['exam_id'] ?? 0, $attempt['student_id'] ?? 0, [
                'anomalies' => $result['anomalies'],
                'checked_at' => $result['checked_at'],
            ]);
        }

        return $result;
    }
}