<?php

namespace Kylin\Notification\Services;

use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
use Kylin\Notification\Channels\AliyunSmsChannel;
use Kylin\Notification\Channels\ApnsChannel;
use Kylin\Notification\Channels\DatabaseChannel;
use Kylin\Notification\Channels\FcmChannel;
use Kylin\Notification\Channels\GeTuiChannel;
use Kylin\Notification\Channels\JPushChannel;
use Kylin\Notification\Channels\MailChannel;
use Kylin\Notification\Channels\TencentSmsChannel;
use Kylin\Notification\Channels\UMengChannel;
use Kylin\Notification\Channels\WechatTemplateChannel;
use Kylin\Notification\Contracts\ChannelInterface;
use Kylin\Notification\Events\NotificationSent;
use Kylin\Notification\Models\Notification;
use Kylin\Notification\Models\NotificationLog;
use Kylin\Notification\Models\NotificationPreference;
use Throwable;

/**
 * 通知服务
 *
 * 统一多渠道通知发送服务，支持：
 *  - 邮件（MailChannel）
 *  - 短信（AliyunSmsChannel / TencentSmsChannel）
 *  - 推送（APNs / FCM / 个推 / 极光 / 友盟）
 *  - 微信模板消息（WechatTemplateChannel）
 *  - 站内信（DatabaseChannel）
 *
 * 所有渠道通过 ChannelInterface 统一接口接入。
 *
 * 法律合规：
 *  - 《个人信息保护法》第四十四条：用户对个人信息处理享有知情权、决定权，
 *    发送前校验用户是否已授权同意（NotificationPreference）
 *  - 《个人信息保护法》第四十七条：用户注销后应停止推送并清理设备信息
 *  - 《通信短信息服务管理规定》第十八条：未经用户同意，不得发送商业性短信息
 *  - 《网络安全法》第二十一条：通知日志全量记录，留存 >= 180 天
 *  - 《网络安全法》第四十二条：不得泄露个人信息，推送内容不包含敏感信息
 */
class NotificationService
{
    /**
     * 渠道名称 → 渠道类映射
     */
    private const CHANNEL_MAP = [
        'database' => DatabaseChannel::class,
        'mail' => MailChannel::class,
        'aliyun_sms' => AliyunSmsChannel::class,
        'tencent_sms' => TencentSmsChannel::class,
        'wechat_template' => WechatTemplateChannel::class,
        'jpush' => JPushChannel::class,
        'getui' => GeTuiChannel::class,
        'umeng' => UMengChannel::class,
        'fcm' => FcmChannel::class,
        'apns' => ApnsChannel::class,
    ];

    /**
     * 敏感信息关键词（用于内容过滤）
     *
     * 依据《网络安全法》第四十二条，不得泄露个人信息。
     */
    private const SENSITIVE_PATTERNS = [
        '/\b\d{15,18}[Xx]?\b/',          // 身份证号
        '/\b1[3-9]\d{9}\b/',              // 手机号
        '/\b\d{16,19}\b/',                // 银行卡号
        '/\b\d{3}-\d{8}|\d{4}-\d{7}\b/',  // 座机
    ];

    /**
     * 发送通知（支持多渠道）
     *
     * 发送前会进行：
     *  1. 用户授权同意校验（PIPL 第 44 条）
     *  2. 敏感内容过滤（网络安全法第 42 条）
     *  3. 渠道解析
     *
     * @param string $type 通知类型
     * @param string $title 标题
     * @param string $content 内容
     * @param array $channels 推送渠道列表，如 ['database', 'aliyun_sms', 'jpush']
     * @param array $options 附加选项
     *   - notifiable_type: 通知目标类型
     *   - notifiable_id: 通知目标ID
     *   - sender_id: 发送者ID
     *   - skip_consent_check: 跳过授权校验（仅限系统级通知，如账号安全）
     *   - channel_params: 各渠道特有参数
     * @return Notification
     */
    public function send(string $type, string $title, string $content, array $channels = [], array $options = []): Notification
    {
        // 默认渠道
        if (empty($channels)) {
            $channels = config('kylin-notification.defaults', ['database']);
        }

        // 敏感内容过滤（网络安全法第 42 条）
        $content = $this->sanitizeContent($content);

        // 创建通知记录
        $notification = Notification::create([
            'type' => $type,
            'title' => $title,
            'content' => $content,
            'notifiable_type' => $options['notifiable_type'] ?? null,
            'notifiable_id' => $options['notifiable_id'] ?? null,
            'sender_id' => $options['sender_id'] ?? null,
            'channels' => $channels,
        ]);

        // 通过各渠道发送
        $channelParams = $options['channel_params'] ?? [];
        $skipConsentCheck = (bool) ($options['skip_consent_check'] ?? false);
        $results = [];
        $skippedChannels = [];

        foreach ($channels as $channel) {
            // 授权同意校验（PIPL 第 44 条）
            if (!$skipConsentCheck && !$this->checkConsent($notification, $channel)) {
                $skippedChannels[] = $channel;
                $results[$channel] = ['success' => false, 'message_id' => null, 'error' => 'User has not consented to this channel', 'raw_response' => null];
                $this->logSendResult($notification, $channel, $results[$channel], []);
                continue;
            }

            $results[$channel] = $this->sendViaChannel($channel, $notification, $channelParams[$channel] ?? []);
        }

        $this->audit('notification_sent', $notification, (int) ($notification->id ?? 0), [
            'channels' => $channels,
            'success_count' => count(array_filter($results, fn($r) => $r['success'] ?? false)),
            'skipped_channels' => $skippedChannels,
            'consent_skipped' => $skipConsentCheck,
        ]);

        return $notification;
    }

    /**
     * 通过指定渠道发送通知
     */
    public function sendViaChannel(string $channelName, Notification $notification, array $params = []): array
    {
        $channelInstance = $this->resolveChannel($channelName);

        if (!$channelInstance) {
            $result = ['success' => false, 'message_id' => null, 'error' => "Unknown channel: {$channelName}", 'raw_response' => null];
        } else {
            $result = $channelInstance->send($notification, $params);
        }

        // 记录发送日志
        $this->logSendResult($notification, $channelName, $result, $params);

        // 触发事件
        Event::dispatch(new NotificationSent(
            notification: $notification,
            channel: $channelName,
            success: $result['success'],
            messageId: $result['message_id'] ?? null,
            error: $result['error'] ?? null,
        ));

        // 失败时记录日志
        if (!$result['success']) {
            Log::warning("Notification send failed via {$channelName}", [
                'notification_id' => $notification->id,
                'error' => $result['error'],
            ]);
        }

        return $result;
    }

    /**
     * 解析渠道实例
     */
    public function resolveChannel(string $channelName): ?ChannelInterface
    {
        // 先查自定义注册
        $customChannels = config('kylin-notification.custom_channels', []);
        if (isset($customChannels[$channelName]) && class_exists($customChannels[$channelName])) {
            return app($customChannels[$channelName]);
        }

        // 内置渠道
        if (isset(self::CHANNEL_MAP[$channelName])) {
            return app(self::CHANNEL_MAP[$channelName]);
        }

        return null;
    }

    /**
     * 获取所有可用渠道
     */
    public function getAvailableChannels(): array
    {
        return array_keys(self::CHANNEL_MAP);
    }

    /**
     * 用户授权同意某渠道（PIPL 第 44 条）
     *
     * @param string $notifiableType 通知目标类型
     * @param int $notifiableId 通知目标ID
     * @param string $channel 渠道名称
     */
    public function grantConsent(string $notifiableType, int $notifiableId, string $channel): NotificationPreference
    {
        $pref = NotificationPreference::grantConsent($notifiableType, $notifiableId, $channel);

        $this->audit('notification_consent_granted', $notification = new Notification(), 0, [
            'notifiable_type' => $notifiableType,
            'notifiable_id' => $notifiableId,
            'channel' => $channel,
        ]);

        return $pref;
    }

    /**
     * 用户撤销某渠道授权（PIPL 第 44 条）
     */
    public function revokeConsent(string $notifiableType, int $notifiableId, string $channel): void
    {
        NotificationPreference::revokeConsent($notifiableType, $notifiableId, $channel);

        $this->audit('notification_consent_revoked', new Notification(), 0, [
            'notifiable_type' => $notifiableType,
            'notifiable_id' => $notifiableId,
            'channel' => $channel,
        ]);
    }

    /**
     * 用户注销时清理通知数据（PIPL 第 47 条）
     *
     * 依据《个人信息保护法》第四十七条，用户注销账号后，
     * 应当停止推送并清理推送设备信息、通知记录、授权同意记录。
     *
     * @param string $notifiableType 通知目标类型
     * @param int $notifiableId 通知目标ID
     * @return array{notifications: int, preferences: int} 清理数量
     */
    public function purgeForUser(string $notifiableType, int $notifiableId): array
    {
        $notificationsCount = Notification::where('notifiable_type', $notifiableType)
            ->where('notifiable_id', $notifiableId)
            ->count();

        $preferencesCount = NotificationPreference::where('notifiable_type', $notifiableType)
            ->where('notifiable_id', $notifiableId)
            ->count();

        // 删除通知记录（含日志通过外键级联）
        Notification::where('notifiable_type', $notifiableType)
            ->where('notifiable_id', $notifiableId)
            ->delete();

        // 删除授权同意记录
        NotificationPreference::where('notifiable_type', $notifiableType)
            ->where('notifiable_id', $notifiableId)
            ->delete();

        $this->audit('notification_user_purged', new Notification(), 0, [
            'notifiable_type' => $notifiableType,
            'notifiable_id' => $notifiableId,
            'notifications_deleted' => $notificationsCount,
            'preferences_deleted' => $preferencesCount,
        ]);

        return [
            'notifications' => $notificationsCount,
            'preferences' => $preferencesCount,
        ];
    }

    /**
     * 记录发送结果
     */
    private function logSendResult(Notification $notification, string $channel, array $result, array $params): NotificationLog
    {
        return NotificationLog::create([
            'notification_id' => $notification->id,
            'channel' => $channel,
            'status' => $result['success'] ? 'success' : 'failed',
            'message_id' => $result['message_id'] ?? null,
            'error' => $result['error'] ?? null,
            'request_params' => $params ?: null,
            'raw_response' => $result['raw_response'] ?? null,
            'sent_at' => now(),
        ]);
    }

    /**
     * 获取用户通知列表
     */
    public function listForUser(string $notifiableType, int $notifiableId, int $perPage = 15)
    {
        return Notification::where('notifiable_type', $notifiableType)
            ->where('notifiable_id', $notifiableId)
            ->orderByDesc('id')
            ->paginate($perPage);
    }

    /**
     * 标记已读
     */
    public function markAsRead(int $id): bool
    {
        $notification = Notification::findOrFail($id);
        $notification->markAsRead();
        return true;
    }

    /**
     * 全部标记已读
     */
    public function markAllAsRead(string $notifiableType, int $notifiableId): int
    {
        return Notification::where('notifiable_type', $notifiableType)
            ->where('notifiable_id', $notifiableId)
            ->whereNull('read_at')
            ->update(['read_at' => now()]);
    }

    /**
     * 未读数量
     */
    public function unreadCount(string $notifiableType, int $notifiableId): int
    {
        return Notification::where('notifiable_type', $notifiableType)
            ->where('notifiable_id', $notifiableId)
            ->whereNull('read_at')
            ->count();
    }

    /**
     * 获取通知发送日志
     */
    public function getLogs(int $notificationId)
    {
        return NotificationLog::where('notification_id', $notificationId)
            ->orderByDesc('id')
            ->get();
    }

    /**
     * 校验用户是否已授权某渠道（PIPL 第 44 条）
     */
    private function checkConsent(Notification $notification, string $channel): bool
    {
        if (!$notification->notifiable_type || !$notification->notifiable_id) {
            // 无明确接收人的系统通知，默认允许
            return true;
        }

        return NotificationPreference::isConsented(
            $notification->notifiable_type,
            (int) $notification->notifiable_id,
            $channel
        );
    }

    /**
     * 敏感内容过滤（网络安全法第 42 条）
     *
     * 将身份证号、手机号、银行卡号等敏感信息替换为星号。
     */
    private function sanitizeContent(string $content): string
    {
        foreach (self::SENSITIVE_PATTERNS as $pattern) {
            $content = preg_replace_callback($pattern, function (array $matches): string {
                $value = $matches[0];
                $length = strlen($value);
                if ($length <= 4) {
                    return str_repeat('*', $length);
                }
                return substr($value, 0, 3) . str_repeat('*', max(1, $length - 6)) . substr($value, -3);
            }, $content) ?? $content;
        }

        return $content;
    }

    /**
     * 记录通知审计日志
     *
     * 依据《网络安全法》第二十一条，通知发送需记录审计日志，便于追溯。
     */
    private function audit(string $event, Notification $notification, int $modelId, array $extra = []): void
    {
        if (!class_exists(\Kylin\AuditLog\Models\AuditLog::class)) {
            return;
        }

        try {
            \Kylin\AuditLog\Models\AuditLog::create([
                'event' => $event,
                'model_type' => Notification::class,
                'model_id' => $notification->id ?: $modelId,
                'new_values' => array_merge([
                    'type' => $notification->type,
                    'title' => $notification->title,
                    'notifiable_type' => $notification->notifiable_type,
                    'notifiable_id' => $notification->notifiable_id,
                ], $extra),
                'old_values' => null,
                'user_id' => auth()->id() ?? $notification->sender_id,
                'ip' => request()?->ip(),
                'url' => request()?->fullUrl(),
                'tag' => 'notification',
            ]);
        } catch (Throwable $e) {
            Log::warning('通知审计日志写入失败', [
                'error' => $e->getMessage(),
                'notification_id' => $notification->id,
                'event' => $event,
            ]);
        }
    }
}
