<?php

/**
 * 文件功能：管理员大卡片通知广播控制器
 *
 * 仅超级管理员（chat.level:super 中间件保护）可调用此接口，
 * 通过 BannerNotification 事件向指定用户或房间推送自定义大卡通知。
 *
 * 安全保证：
 *  - 路由被 ['chat.auth', 'chat.has_position', 'chat.level:super'] 三层中间件保护
 *  - 普通用户无权访问此接口，无法伪造对他人的广播
 *  - options 中的用户输入字段在后端统一降级为纯文本 / 白名单样式值
 *
 * @author   ChatRoom Laravel
 *
 * @version  1.0.0
 */

namespace App\Http\Controllers\Admin;

use App\Events\BannerNotification;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

/**
 * 类功能：安全地下发大卡片广播消息。
 */
class BannerBroadcastController extends Controller
{
    /**
     * 向指定目标广播大卡片通知。
     *
     * 请求参数：
     *  - target:    'user' | 'room'
     *  - target_id: 用户名 或 房间 ID
     *  - options:   与 window.chatBanner.show() 参数相同的对象
     *    - icon, title, name, body, sub, gradient(array), titleColor, autoClose, buttons(array)
     */
    public function send(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'target' => ['required', 'in:user,room'],
            'target_id' => ['required'],
            'options' => ['required', 'array'],
            'options.icon' => ['nullable', 'string', 'max:20'],
            'options.title' => ['nullable', 'string', 'max:50'],
            'options.name' => ['nullable', 'string', 'max:100'],
            'options.body' => ['nullable', 'string', 'max:500'],
            'options.sub' => ['nullable', 'string', 'max:200'],
            'options.gradient' => ['nullable', 'array', 'max:5'],
            'options.gradient.*' => ['nullable', 'string', 'max:30'],
            'options.titleColor' => ['nullable', 'string', 'max:30'],
            'options.autoClose' => ['nullable', 'integer', 'min:0', 'max:30000'],
            'options.buttons' => ['nullable', 'array', 'max:4'],
            'options.buttons.*.label' => ['nullable', 'string', 'max:30'],
            'options.buttons.*.color' => ['nullable', 'string', 'max:30'],
            'options.buttons.*.action' => ['nullable', 'string', 'max:20'],
        ]);

        // 所有可见文案一律降级为纯文本，避免允许标签残留属性后在前端 innerHTML 中执行。
        $opts = $validated['options'];
        foreach (['title', 'name', 'body', 'sub'] as $field) {
            if (isset($opts[$field])) {
                $opts[$field] = $this->sanitizeBannerText($opts[$field]);
            }
        }

        if (isset($opts['titleColor'])) {
            $opts['titleColor'] = $this->sanitizeCssValue($opts['titleColor'], '#fde68a');
        }

        if (! empty($opts['gradient'])) {
            $opts['gradient'] = array_values(array_map(
                fn ($color) => $this->sanitizeCssValue($color, '#4f46e5'),
                $opts['gradient']
            ));
        }

        // 按钮 label 与颜色都只允许安全文本 / 颜色值。
        if (! empty($opts['buttons'])) {
            $opts['buttons'] = array_map(function ($btn) {
                $btn['label'] = $this->sanitizeBannerText($btn['label'] ?? '');
                $btn['color'] = $this->sanitizeCssValue($btn['color'] ?? '#10b981', '#10b981');
                // action 只允许预定义值，防止注入任意 JS
                $btn['action'] = in_array($btn['action'] ?? '', ['close', 'add_friend', 'remove_friend', 'link'])
                    ? $btn['action'] : 'close';

                return $btn;
            }, $opts['buttons']);
        }

        broadcast(new BannerNotification(
            target: $validated['target'],
            targetId: $validated['target_id'],
            options: $opts,
        ));

        return response()->json(['status' => 'success', 'message' => '广播已发送']);
    }

    /**
     * 将 Banner 文案净化为安全纯文本。
     */
    private function sanitizeBannerText(?string $text): string
    {
        return trim(strip_tags((string) $text));
    }

    /**
     * 清洗颜色 / 渐变等 CSS 值，阻断样式属性注入。
     */
    private function sanitizeCssValue(?string $value, string $default): string
    {
        $sanitized = strtolower(trim((string) $value));
        if ($sanitized === '' || preg_match('/(?:javascript|expression|url\s*\(|data:|var\s*\()/i', $sanitized)) {
            return $default;
        }

        $allowedPatterns = [
            '/^#[0-9a-f]{3,8}$/i',
            '/^rgba?\(\s*(?:\d{1,3}\s*,\s*){2}\d{1,3}(?:\s*,\s*(?:0|1|0?\.\d+))?\s*\)$/i',
            '/^hsla?\(\s*\d{1,3}(?:deg)?\s*,\s*\d{1,3}%\s*,\s*\d{1,3}%(?:\s*,\s*(?:0|1|0?\.\d+))?\s*\)$/i',
            '/^(?:white|black|red|blue|green|gray|grey|yellow|orange|pink|purple|teal|cyan|indigo|amber|emerald|transparent|currentcolor)$/i',
        ];

        foreach ($allowedPatterns as $allowedPattern) {
            if (preg_match($allowedPattern, $sanitized)) {
                return $sanitized;
            }
        }

        return $default;
    }
}
