<?php

/**
 * 文件功能：聊天室快捷任命控制器测试
 *
 * 验证聊天室用户名片中的任命/撤销操作会给目标用户写入带右下角提示的私聊消息。
 */

namespace Tests\Feature;

use App\Events\UserBrowserRefreshRequested;
use App\Jobs\SaveMessageJob;
use App\Models\Department;
use App\Models\Position;
use App\Models\Room;
use App\Models\User;
use App\Models\UserPosition;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Redis;
use Tests\TestCase;

/**
 * 聊天室快捷任命控制器测试类。
 */
class ChatAppointmentControllerTest extends TestCase
{
    use RefreshDatabase;

    /**
     * 每个测试前清空 Redis，避免跨用例的聊天室消息污染断言。
     */
    protected function setUp(): void
    {
        parent::setUp();
        Redis::flushall();
    }

    /**
     * 测试任命职务后会给目标用户写入带 toast 的私聊提示。
     */
    public function test_appoint_pushes_private_toast_notification_to_target(): void
    {
        Event::fake([UserBrowserRefreshRequested::class]);
        Queue::fake();

        [$admin, $target, $room, $position] = $this->createAppointmentActors();

        $response = $this->actingAs($admin)->postJson(route('chat.appoint.appoint'), [
            'username' => $target->username,
            'position_id' => $position->id,
            'remark' => '聊天室快速任命',
            'room_id' => $room->id,
        ]);

        $response->assertOk()->assertJson([
            'status' => 'success',
            'message' => "已成功将【{$target->username}】任命为【{$position->name}】。",
        ]);

        $privateMessage = $this->findPrivateSystemMessage($room->id, $target->username, '已任命你为');

        $this->assertNotNull($privateMessage);
        $this->assertSame('✨ 职务任命通知', $privateMessage['toast_notification']['title'] ?? null);
        $this->assertSame('✨', $privateMessage['toast_notification']['icon'] ?? null);
        $this->assertSame('#a855f7', $privateMessage['toast_notification']['color'] ?? null);

        Event::assertDispatched(UserBrowserRefreshRequested::class, function (UserBrowserRefreshRequested $event) use ($target, $admin) {
            return $event->targetUserId === $target->id
                && $event->operator === $admin->username
                && $event->reason === '你的职务已发生变更，页面权限正在同步更新。';
        });

        Queue::assertPushed(SaveMessageJob::class, 1);
    }

    /**
     * 测试撤销职务后会给目标用户写入带 toast 的私聊提示。
     */
    public function test_revoke_pushes_private_toast_notification_to_target(): void
    {
        Event::fake([UserBrowserRefreshRequested::class]);
        Queue::fake();

        [$admin, $target, $room, $position] = $this->createAppointmentActors();

        UserPosition::create([
            'user_id' => $target->id,
            'position_id' => $position->id,
            'appointed_by_user_id' => $admin->id,
            'appointed_at' => now()->subDay(),
            'remark' => '已有职务',
            'is_active' => true,
        ]);
        $target->update(['user_level' => $position->level]);

        $response = $this->actingAs($admin)->postJson(route('chat.appoint.revoke'), [
            'username' => $target->username,
            'remark' => '聊天室快速撤销',
            'room_id' => $room->id,
        ]);

        $response->assertOk()->assertJson([
            'status' => 'success',
        ]);

        $privateMessage = $this->findPrivateSystemMessage($room->id, $target->username, '已撤销你的');

        $this->assertNotNull($privateMessage);
        $this->assertSame('📋 职务变动通知', $privateMessage['toast_notification']['title'] ?? null);
        $this->assertSame('📋', $privateMessage['toast_notification']['icon'] ?? null);
        $this->assertSame('#6b7280', $privateMessage['toast_notification']['color'] ?? null);

        Event::assertDispatched(UserBrowserRefreshRequested::class, function (UserBrowserRefreshRequested $event) use ($target, $admin) {
            return $event->targetUserId === $target->id
                && $event->operator === $admin->username
                && $event->reason === '你的职务已被撤销，页面权限正在同步更新。';
        });

        Queue::assertPushed(SaveMessageJob::class, 1);
    }

    /**
     * 创建任命测试共用的操作者、目标用户、房间与职务。
     *
     * @return array{0: User, 1: User, 2: Room, 3: Position}
     */
    private function createAppointmentActors(): array
    {
        $admin = User::factory()->create([
            'id' => 1,
            'user_level' => 100,
        ]);
        $target = User::factory()->create([
            'user_level' => 1,
        ]);
        $room = Room::create([
            'room_name' => '职务操作房',
        ]);
        $department = Department::create([
            'name' => '办公厅',
            'rank' => 90,
        ]);
        $position = Position::create([
            'department_id' => $department->id,
            'name' => '巡查员',
            'icon' => '✨',
            'rank' => 50,
            'level' => 20,
        ]);

        return [$admin, $target, $room, $position];
    }

    /**
     * 从房间消息缓存中定位目标用户收到的系统私聊提示。
     *
     * @return array<string, mixed>|null
     */
    private function findPrivateSystemMessage(int $roomId, string $targetUsername, string $needle): ?array
    {
        $messages = Redis::lrange("room:{$roomId}:messages", 0, -1);

        return collect($messages)
            ->map(fn (string $item) => json_decode($item, true))
            ->first(fn (array $item) => ($item['from_user'] ?? null) === '系统'
                && ($item['to_user'] ?? null) === $targetUsername
                && str_contains((string) ($item['content'] ?? ''), $needle));
    }
}
