<?php

/**
 * 文件功能：商店控制器功能测试
 * 覆盖商品列表、购买、改名卡与新增特效商品展示等商店流程。
 */

namespace Tests\Feature;

use App\Events\EffectBroadcast;
use App\Events\MessageSent;
use App\Models\Room;
use App\Models\ShopItem;
use App\Models\User;
use App\Models\UserPurchase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Redis;
use Tests\TestCase;

/**
 * 商店控制器功能测试
 * 验证商店接口对商品与购买逻辑的返回结果。
 */
class ShopControllerTest extends TestCase
{
    use RefreshDatabase;

    /**
     * 每个测试前清空 Redis，避免房间在线状态串扰。
     */
    protected function setUp(): void
    {
        parent::setUp();
        Redis::flushall();
    }

    /**
     * 测试商品列表接口只返回上架商品。
     */
    public function test_items_returns_active_shop_items()
    {
        $user = User::factory()->create();

        $activeItem = ShopItem::create([
            'name' => 'Active',
            'slug' => 'active_item',
            'type' => 'one_time',
            'price' => 100,
            'is_active' => true,
        ]);

        $inactiveItem = ShopItem::create([
            'name' => 'Inactive',
            'slug' => 'inactive_item',
            'type' => 'one_time',
            'price' => 100,
            'is_active' => false,
        ]);

        $response = $this->actingAs($user)->getJson(route('shop.items'));

        $response->assertStatus(200);

        $responseItems = collect($response->json('items'));
        $this->assertTrue($responseItems->contains('id', $activeItem->id));
        $this->assertFalse($responseItems->contains('id', $inactiveItem->id));
    }

    /**
     * 测试商店商品列表会包含新增的特效单次卡与周卡。
     */
    public function test_items_include_new_effect_shop_cards(): void
    {
        $user = User::factory()->create();

        $response = $this->actingAs($user)->getJson(route('shop.items'));

        $response->assertOk();
        $response->assertJsonFragment(['slug' => 'once_meteors']);
        $response->assertJsonFragment(['slug' => 'once_gold-rain']);
        $response->assertJsonFragment(['slug' => 'once_hearts']);
        $response->assertJsonFragment(['slug' => 'once_confetti']);
        $response->assertJsonFragment(['slug' => 'once_fireflies']);
        $response->assertJsonFragment(['slug' => 'once_sakura']);
        $response->assertJsonFragment(['slug' => 'week_sakura']);
        $response->assertJsonFragment(['slug' => 'week_meteors']);
        $response->assertJsonFragment(['slug' => 'week_gold-rain']);
        $response->assertJsonFragment(['slug' => 'week_hearts']);
        $response->assertJsonFragment(['slug' => 'week_confetti']);
        $response->assertJsonFragment(['slug' => 'week_fireflies']);
    }

    /**
     * 测试一次性道具可以正常购买。
     */
    public function test_can_buy_one_time_item()
    {
        $user = User::factory()->create(['jjb' => 500]);
        $room = Room::create(['room_name' => '购买房']);
        $this->joinRoom($user, $room);

        $item = ShopItem::firstOrCreate(
            ['slug' => 'rename_card_test'],
            [
                'name' => 'Rename Card Test',
                'type' => 'one_time',
                'price' => 100,
                'is_active' => true,
            ]);

        $response = $this->actingAs($user)->postJson(route('shop.buy'), [
            'item_id' => $item->id,
            'room_id' => $room->id,
        ]);

        $response->assertStatus(200);
        $response->assertJson(['status' => 'success']);

        $this->assertDatabaseHas('user_purchases', [
            'user_id' => $user->id,
            'shop_item_id' => $item->id,
        ]);

        $this->assertDatabaseHas('users', [
            'id' => $user->id,
            'jjb' => 400,
        ]);
    }

    /**
     * 测试余额不足时不能购买商品。
     */
    public function test_cannot_buy_if_insufficient_funds()
    {
        $user = User::factory()->create(['jjb' => 50]);
        $room = Room::create(['room_name' => '余额不足房']);
        $this->joinRoom($user, $room);

        $item = ShopItem::firstOrCreate(
            ['slug' => 'rename_card_test'],
            [
                'name' => 'Rename Card Test',
                'type' => 'one_time',
                'price' => 100,
                'is_active' => true,
            ]);

        $response = $this->actingAs($user)->postJson(route('shop.buy'), [
            'item_id' => $item->id,
            'room_id' => $room->id,
        ]);

        $response->assertStatus(400);
        $response->assertJson(['status' => 'error']);

        $this->assertDatabaseMissing('user_purchases', [
            'user_id' => $user->id,
            'shop_item_id' => $item->id,
        ]);
    }

    /**
     * 测试已下架商品不能购买。
     */
    public function test_cannot_buy_inactive_item()
    {
        $user = User::factory()->create(['jjb' => 500]);
        $room = Room::create(['room_name' => '下架商品房']);
        $this->joinRoom($user, $room);

        $item = ShopItem::create([
            'name' => 'Old Card',
            'slug' => 'old_card',
            'type' => 'one_time',
            'price' => 100,
            'is_active' => false,
        ]);

        $response = $this->actingAs($user)->postJson(route('shop.buy'), [
            'item_id' => $item->id,
            'room_id' => $room->id,
        ]);

        $response->assertStatus(400);

        $this->assertDatabaseMissing('user_purchases', [
            'user_id' => $user->id,
        ]);
    }

    /**
     * 测试改名卡可以被正常使用。
     */
    public function test_can_use_rename_card()
    {
        $user = User::factory()->create(['username' => 'OldName']);

        // Actually the service hardcodes 'rename_card' slug check: $item->slug === 'rename_card'
        // So we MUST use 'rename_card'
        $item = ShopItem::firstOrCreate(
            ['slug' => 'rename_card'],
            [
                'name' => 'Rename Card',
                'type' => 'one_time',
                'price' => 100,
                'is_active' => true,
            ]);

        UserPurchase::create([
            'user_id' => $user->id,
            'shop_item_id' => $item->id,
            'status' => 'active',
            'used_at' => null,
            'cost_amount' => 100,
            'currency_type' => 'gold',
        ]);

        $response = $this->actingAs($user)->postJson(route('shop.rename'), [
            'new_name' => 'NewName',
        ]);

        $response->assertStatus(200);
        $response->assertJson(['status' => 'success']);

        // Assert user's name is updated
        $this->assertDatabaseHas('users', [
            'id' => $user->id,
            'username' => 'NewName',
        ]);

        // Assert card is used
        $this->assertDatabaseHas('user_purchases', [
            'user_id' => $user->id,
            'shop_item_id' => $item->id,
            'status' => 'used',
        ]);
    }

    /**
     * 测试购买自动钓鱼卡时会以钓鱼播报身份广播，便于前端屏蔽规则命中。
     */
    public function test_buy_auto_fishing_card_broadcasts_as_fishing_sender(): void
    {
        Event::fake([MessageSent::class]);

        $user = User::factory()->create(['jjb' => 500]);
        $room = Room::create(['room_name' => '自动钓鱼房']);
        $this->joinRoom($user, $room);

        $item = ShopItem::create([
            'name' => '自动钓鱼卡（2小时）',
            'slug' => 'auto_fishing_test_2h',
            'type' => 'auto_fishing',
            'price' => 100,
            'duration_minutes' => 120,
            'is_active' => true,
        ]);

        $response = $this->actingAs($user)->postJson(route('shop.buy'), [
            'item_id' => $item->id,
            'room_id' => $room->id,
        ]);

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

        Event::assertDispatched(MessageSent::class, function (MessageSent $event) use ($user, $item, $room): bool {
            return $event->roomId === $room->id
                && ($event->message['from_user'] ?? null) === '钓鱼播报'
                && str_contains((string) ($event->message['content'] ?? ''), $user->username)
                && str_contains((string) ($event->message['content'] ?? ''), $item->name);
        });
    }

    /**
     * 测试指定接收人购买单次特效时，购买者本端仍会拿到播放指令，且广播会带上接收人与操作者信息。
     */
    public function test_buy_instant_effect_for_recipient_returns_local_play_and_broadcasts_targeted_event(): void
    {
        Event::fake([EffectBroadcast::class]);

        $buyer = User::factory()->create([
            'username' => 'buyer-user',
            'jjb' => 5000,
        ]);
        $room = Room::create(['room_name' => '指定特效房']);
        $this->joinRoom($buyer, $room);
        $recipient = User::factory()->create([
            'username' => 'receiver-user',
        ]);

        $item = ShopItem::create([
            'name' => '烟花单次卡',
            'slug' => 'once_fireworks_targeted',
            'type' => 'instant',
            'price' => 888,
            'icon' => '🎆',
            'is_active' => true,
        ]);

        $response = $this->actingAs($buyer)->postJson(route('shop.buy'), [
            'item_id' => $item->id,
            'room_id' => $room->id,
            'recipient' => $recipient->username,
            'message' => '送你一场烟花',
        ]);

        $response->assertOk();
        $response->assertJson([
            'status' => 'success',
            'play_effect' => $item->effectKey(),
            'target_username' => $recipient->username,
            'gift_message' => '送你一场烟花',
        ]);

        Event::assertDispatched(EffectBroadcast::class, function (EffectBroadcast $event) use ($buyer, $recipient, $item, $room): bool {
            return $event->roomId === $room->id
                && $event->type === $item->effectKey()
                && $event->operator === $buyer->username
                && $event->targetUsername === $recipient->username
                && $event->giftMessage === '送你一场烟花'
                && $event->broadcastWith()['operator'] === $buyer->username;
        });
    }

    /**
     * 测试定向特效的目标用户名作为标识字段时保持原始值，避免前端比较失败。
     */
    public function test_buy_instant_effect_keeps_raw_target_username_identifier(): void
    {
        Event::fake([EffectBroadcast::class]);

        $buyer = User::factory()->create([
            'username' => 'buyer-user-raw',
            'jjb' => 5000,
        ]);
        $room = Room::create(['room_name' => '原始目标名房']);
        $this->joinRoom($buyer, $room);
        $recipient = User::factory()->create([
            'username' => 'receiver&user',
        ]);
        $item = ShopItem::create([
            'name' => '烟花单次卡',
            'slug' => 'once_fireworks_raw_target',
            'type' => 'instant',
            'price' => 888,
            'is_active' => true,
        ]);

        $response = $this->actingAs($buyer)->postJson(route('shop.buy'), [
            'item_id' => $item->id,
            'room_id' => $room->id,
            'recipient' => $recipient->username,
        ]);

        $response->assertOk()
            ->assertJsonPath('target_username', $recipient->username);

        Event::assertDispatched(EffectBroadcast::class, function (EffectBroadcast $event) use ($recipient): bool {
            return $event->targetUsername === $recipient->username;
        });
    }

    /**
     * 测试购买特效时缺少房间 ID 会被拒绝，避免广播到 room.0。
     */
    public function test_buy_instant_effect_requires_room_id(): void
    {
        $buyer = User::factory()->create([
            'jjb' => 5000,
        ]);
        $item = ShopItem::create([
            'name' => '烟花单次卡',
            'slug' => 'once_fireworks_missing_room',
            'type' => 'instant',
            'price' => 888,
            'is_active' => true,
        ]);

        $response = $this->actingAs($buyer)->postJson(route('shop.buy'), [
            'item_id' => $item->id,
        ]);

        $response->assertStatus(422);
    }

    /**
     * 测试购买特效的赠言会转义后再进入广播载荷。
     */
    public function test_buy_instant_effect_escapes_gift_message(): void
    {
        Event::fake([EffectBroadcast::class, MessageSent::class]);

        $buyer = User::factory()->create([
            'jjb' => 5000,
        ]);
        $room = Room::create(['room_name' => '赠言安全房']);
        $this->joinRoom($buyer, $room);
        $item = ShopItem::create([
            'name' => '烟花单次卡',
            'slug' => 'once_fireworks_safe_message',
            'type' => 'instant',
            'price' => 888,
            'is_active' => true,
        ]);

        $response = $this->actingAs($buyer)->postJson(route('shop.buy'), [
            'item_id' => $item->id,
            'room_id' => $room->id,
            'recipient' => 'all',
            'message' => '<img src=x onerror=alert(1)>',
        ]);

        $response->assertOk()
            ->assertJsonPath('gift_message', '&lt;img src=x onerror=alert(1)&gt;');

        Event::assertDispatched(EffectBroadcast::class, function (EffectBroadcast $event): bool {
            return $event->giftMessage === '&lt;img src=x onerror=alert(1)&gt;';
        });
        Event::assertDispatched(MessageSent::class, function (MessageSent $event): bool {
            return str_contains((string) ($event->message['content'] ?? ''), '&lt;img src=x onerror=alert(1)&gt;')
                && ! str_contains((string) ($event->message['content'] ?? ''), '<img src=x onerror=alert(1)>');
        });
    }

    /**
     * 测试购买签到补签卡会扣金币并生成可用背包记录。
     */
    public function test_buy_sign_repair_card_creates_active_purchase(): void
    {
        $user = User::factory()->create(['jjb' => 12000]);
        $room = Room::create(['room_name' => '补签卡房']);
        $this->joinRoom($user, $room);
        $item = ShopItem::query()->where('slug', 'sign_repair_card')->firstOrFail();
        $item->update([
            'price' => 10000,
            'is_active' => true,
        ]);

        $response = $this->actingAs($user)->postJson(route('shop.buy'), [
            'item_id' => $item->id,
            'room_id' => $room->id,
        ]);

        $response->assertOk()
            ->assertJsonPath('status', 'success')
            ->assertJsonPath('jjb', 2000);

        $this->assertDatabaseHas('user_purchases', [
            'user_id' => $user->id,
            'shop_item_id' => $item->id,
            'status' => 'active',
            'price_paid' => 10000,
        ]);
    }

    /**
     * 测试补签卡支持一次购买多张。
     */
    public function test_buy_sign_repair_card_supports_quantity(): void
    {
        $user = User::factory()->create(['jjb' => 35000]);
        $room = Room::create(['room_name' => '多张补签卡房']);
        $this->joinRoom($user, $room);
        $item = ShopItem::query()->where('slug', 'sign_repair_card')->firstOrFail();
        $item->update([
            'price' => 10000,
            'is_active' => true,
        ]);

        $response = $this->actingAs($user)->postJson(route('shop.buy'), [
            'item_id' => $item->id,
            'room_id' => $room->id,
            'quantity' => 3,
        ]);

        $response->assertOk()
            ->assertJsonPath('status', 'success')
            ->assertJsonPath('quantity', 3)
            ->assertJsonPath('total_price', 30000)
            ->assertJsonPath('jjb', 5000);

        $this->assertSame(3, UserPurchase::query()
            ->where('user_id', $user->id)
            ->where('shop_item_id', $item->id)
            ->where('status', 'active')
            ->count());
    }

    /**
     * 将测试用户写入指定房间在线表，模拟已经进入聊天室的状态。
     */
    private function joinRoom(User $user, Room $room): void
    {
        Redis::hset("room:{$room->id}:users", $user->username, json_encode([
            'id' => $user->id,
            'username' => $user->username,
        ], JSON_UNESCAPED_UNICODE));
    }
}
