<?php

namespace Kylin\Settings\Services;

use Illuminate\Support\Facades\Crypt;
use Kylin\Settings\Events\SettingChanged;
use Kylin\Settings\Models\Setting;

/**
 * 系统设置服务
 *
 * 提供系统配置的读取和写入操作。
 *
 * 合规要求：
 *  - 依据等保 2.0 (GB/T 22239) 8.1.4.6，配置变更需记录审计日志
 *  - 依据《密码法》第二十七条，密钥/Token 类配置需加密存储
 *  - 依据《数据安全法》第二十七条，敏感配置变更需通知
 */
class SettingsService
{
    /**
     * 敏感配置分组列表
     * 此分组下的配置变更将触发 SettingChanged 事件
     */
    protected array $sensitiveGroups = [
        'security',
        'payment',
        'third_party',
        'sms',
        'email',
    ];

    public function get(string $key, mixed $default = null, string $group = 'default'): mixed
    {
        $value = Setting::getValue($key, $default, $group);

        // 加密类型字段自动解密
        if ($value !== null && $value !== $default) {
            $setting = Setting::where('group', $group)->where('key', $key)->first();
            if ($setting && $setting->type === 'encrypted') {
                try {
                    $value = Crypt::decryptString($value);
                } catch (\Throwable) {
                    $value = $default;
                }
            }
        }

        return $value;
    }

    public function set(string $key, mixed $value, string $type = 'string', string $group = 'default', ?string $description = null): Setting
    {
        $oldValue = $this->getExistingValue($key, $group);

        // 加密类型：自动加密存储
        $storeValue = $value;
        if ($type === 'encrypted') {
            $storeValue = Crypt::encryptString((string) $value);
        }

        $setting = Setting::setValue($key, $storeValue, $type, $group, $description);

        // 审计日志：配置变更
        $this->audit('setting_updated', Setting::class, $setting->id, [
            'group' => $group,
            'key' => $key,
            'type' => $type,
            'new_value' => $type === 'encrypted' ? '[ENCRYPTED]' : $this->maskSensitiveValue($group, $key, $storeValue),
            'old_value' => $oldValue !== null ? ($type === 'encrypted' ? '[ENCRYPTED]' : $this->maskSensitiveValue($group, $key, $oldValue)) : null,
        ]);

        // 敏感配置变更事件
        if (in_array($group, $this->sensitiveGroups, true)) {
            SettingChanged::dispatch(
                $group,
                $key,
                $type === 'encrypted' ? '[ENCRYPTED]' : $this->maskSensitiveValue($group, $key, $storeValue),
                $oldValue !== null ? ($type === 'encrypted' ? '[ENCRYPTED]' : $this->maskSensitiveValue($group, $key, $oldValue)) : null,
                auth()->id(),
            );
        }

        return $setting;
    }

    public function getGroup(string $group): array
    {
        $settings = Setting::where('group', $group)->get()->pluck('value', 'key')->toArray();

        // 解密加密类型字段
        foreach ($settings as $key => $value) {
            $setting = Setting::where('group', $group)->where('key', $key)->first();
            if ($setting && $setting->type === 'encrypted') {
                try {
                    $settings[$key] = Crypt::decryptString($value);
                } catch (\Throwable) {
                    $settings[$key] = null;
                }
            }
        }

        return $settings;
    }

    public function delete(string $key, string $group = 'default'): bool
    {
        $oldValue = $this->getExistingValue($key, $group);
        $result = Setting::where('group', $group)->where('key', $key)->delete() > 0;

        // 审计日志：配置删除
        if ($result) {
            $this->audit('setting_deleted', Setting::class, 0, null, [
                'group' => $group,
                'key' => $key,
                'old_value' => $oldValue !== null ? $this->maskSensitiveValue($group, $key, $oldValue) : null,
            ]);
        }

        return $result;
    }

    /**
     * 获取配置项当前值
     */
    private function getExistingValue(string $key, string $group): ?string
    {
        $setting = Setting::where('group', $group)->where('key', $key)->first();
        return $setting?->value;
    }

    /**
     * 敏感值脱敏
     *
     * 依据《个人信息保护法》第五十一条，对敏感信息脱敏处理。
     */
    private function maskSensitiveValue(string $group, string $key, mixed $value): mixed
    {
        if (! in_array($group, $this->sensitiveGroups, true)) {
            return $value;
        }

        $sensitiveKeys = ['secret', 'key', 'token', 'password', 'api_key', 'app_secret'];
        foreach ($sensitiveKeys as $sensitive) {
            if (stripos($key, $sensitive) !== false) {
                $str = (string) $value;
                if (mb_strlen($str) > 8) {
                    return mb_substr($str, 0, 4) . '****' . mb_substr($str, -4);
                }
                return '****';
            }
        }

        return $value;
    }

    /**
     * 写入审计日志
     *
     * 依据等保 2.0 (GB/T 22239) 8.1.4.6：
     * 配置变更操作需记录审计日志。
     */
    private function audit(string $event, string $modelType, int|string $modelId, ?array $newValues = null, ?array $oldValues = null): void
    {
        if (! class_exists(\Kylin\AuditLog\Models\AuditLog::class)) {
            return;
        }

        try {
            \Kylin\AuditLog\Models\AuditLog::create([
                'event' => $event,
                'model_type' => $modelType,
                'model_id' => $modelId,
                'old_values' => $oldValues,
                'new_values' => $newValues,
                'user_id' => auth()->id(),
                'ip' => request()->ip(),
                'url' => request()->fullUrl(),
                'tag' => 'settings',
            ]);
        } catch (\Throwable $e) {
            logger()->warning('系统设置审计日志写入失败', [
                'event' => $event,
                'error' => $e->getMessage(),
            ]);
        }
    }
}