<?php

namespace Kylin\Activity\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Collection;
use Kylin\Activity\ActivitylogServiceProvider;

/**
 * 日志脱敏审计命令
 *
 * Artisan 命令：php artisan activitylog:audit-redaction
 *
 * 依据《个人信息保护法》第五十一条，定期审计脱敏日志，
 * 确保无敏感信息泄露。扫描指定时间范围内的日志记录，
 * 检测是否包含未脱敏的敏感字段。
 *
 * 参数：
 *  --days：扫描最近 N 天的日志（默认 30 天）
 *  --log：指定日志名称（可选）
 *  --fix：自动修复检测到的问题（重新脱敏）
 */
class AuditRedactionCommand extends Command
{
    /** 命令签名 */
    protected $signature = 'activitylog:audit-redaction
                            {--days=30 : 扫描最近 N 天的日志}
                            {--log= : 指定日志名称}
                            {--fix : 自动修复检测到的问题}';

    /** 命令描述 */
    protected $description = '审计活动日志中的敏感信息脱敏质量';

    /**
     * 执行审计命令
     */
    public function handle(): int
    {
        $days = (int) $this->option('days');
        $logName = $this->option('log');
        $shouldFix = $this->option('fix');

        $this->info("审计最近 {$days} 天的活动日志脱敏质量...");

        $sensitiveFields = config('kylin-activity.sensitive_fields', []);
        if (empty($sensitiveFields)) {
            $this->warn('未配置敏感字段列表，无法执行审计。');

            return 1;
        }

        $activity = ActivitylogServiceProvider::getActivityModelInstance();

        $query = $activity::query()
            ->where('created_at', '>=', now()->subDays($days));

        if ($logName !== null) {
            $query->inLog($logName);
        }

        $records = $query->get();

        $violations = $this->scanRecords($records, $sensitiveFields);

        if ($violations->isEmpty()) {
            $this->info('未发现脱敏质量问题。所有日志记录均符合脱敏要求。');

            return 0;
        }

        $this->table(
            ['ID', '日志名称', '描述', '敏感字段', '创建时间'],
            $violations->take(50)->map(function ($v) {
                return [
                    $v['id'],
                    $v['log_name'],
                    $v['description'],
                    implode(', ', $v['fields']),
                    $v['created_at'],
                ];
            })->toArray()
        );

        $this->warn("发现 {$violations->count()} 条日志记录存在脱敏质量问题。");

        if ($shouldFix) {
            $this->fixViolations($violations, $records, $sensitiveFields);
        } else {
            $this->comment('使用 --fix 参数可自动修复检测到的问题。');
        }

        return 0;
    }

    /**
     * 扫描日志记录，检测未脱敏的敏感字段
     */
    protected function scanRecords(Collection $records, array $sensitiveFields): Collection
    {
        $violations = collect();

        foreach ($records as $record) {
            $properties = $record->properties;
            if ($properties === null || $properties->isEmpty()) {
                continue;
            }

            $foundFields = $this->findUnredactedFields($properties, $sensitiveFields);

            if (! empty($foundFields)) {
                $violations->push([
                    'id' => $record->id,
                    'log_name' => $record->log_name,
                    'description' => $record->description,
                    'fields' => $foundFields,
                    'created_at' => $record->created_at->toDateTimeString(),
                    'record' => $record,
                ]);
            }
        }

        return $violations;
    }

    /**
     * 查找未脱敏的敏感字段
     */
    protected function findUnredactedFields(Collection $properties, array $sensitiveFields): array
    {
        $found = [];

        foreach ($properties as $key => $value) {
            $keyLower = mb_strtolower($key);

            foreach ($sensitiveFields as $sensitive) {
                $sensitiveLower = mb_strtolower($sensitive);

                if ($keyLower === $sensitiveLower || str_contains($keyLower, $sensitiveLower)) {
                    // 检查值是否为脱敏标记
                    if (is_string($value) && $value !== '[REDACTED]') {
                        $found[] = $key;
                    }
                    if (is_array($value)) {
                        foreach ($value as $subKey => $subValue) {
                            $subKeyLower = mb_strtolower((string) $subKey);
                            if ($subKeyLower === $sensitiveLower || str_contains($subKeyLower, $sensitiveLower)) {
                                if (is_string($subValue) && $subValue !== '[REDACTED]') {
                                    $found[] = "{$key}.{$subKey}";
                                }
                            }
                        }
                    }
                }
            }
        }

        return array_unique($found);
    }

    /**
     * 修复违规记录（重新脱敏）
     */
    protected function fixViolations(Collection $violations, Collection $records, array $sensitiveFields): void
    {
        $fixedCount = 0;

        foreach ($violations as $violation) {
            $record = $violation['record'];
            $properties = $record->properties;

            $redacted = $properties->map(function ($value, $key) use ($sensitiveFields) {
                if (is_array($value)) {
                    return collect($value)->map(function ($v, $k) use ($sensitiveFields) {
                        return $this->isSensitiveKey($k, $sensitiveFields) ? '[REDACTED]' : $v;
                    })->toArray();
                }

                if ($this->isSensitiveKey($key, $sensitiveFields)) {
                    return '[REDACTED]';
                }

                return $value;
            });

            $record->properties = $redacted;
            $record->save();
            $fixedCount++;
        }

        $this->info("已修复 {$fixedCount} 条日志记录的脱敏问题。");
    }

    /**
     * 判断键名是否为敏感字段
     */
    protected function isSensitiveKey(string $key, array $sensitiveFields): bool
    {
        $keyLower = mb_strtolower($key);

        foreach ($sensitiveFields as $sensitive) {
            $sensitiveLower = mb_strtolower($sensitive);
            if ($keyLower === $sensitiveLower || str_contains($keyLower, $sensitiveLower)) {
                return true;
            }
        }

        return false;
    }
}