<?php

/**
 * 文件功能：百家乐前台接口与结算通知测试
 *
 * 覆盖下注、历史查询以及开奖后参与者的私聊提示行为。
 */

namespace Tests\Feature;

use App\Jobs\CloseBaccaratRoundJob;
use App\Jobs\SaveMessageJob;
use App\Models\BaccaratBet;
use App\Models\BaccaratRound;
use App\Models\GameConfig;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Redis;
use Tests\TestCase;

/**
 * 百家乐控制器与结算流程测试类。
 */
class BaccaratControllerTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();

        GameConfig::updateOrCreate(
            ['game_key' => 'baccarat'],
            [
                'name' => 'Baccarat',
                'icon' => 'baccarat',
                'description' => 'Baccarat Game',
                'enabled' => true,
                'params' => [
                    'min_bet' => 100,
                    'max_bet' => 50000,
                ],
            ]
        );
    }

    public function test_can_get_current_round()
    {
        /** @var \App\Models\User $user */
        $user = User::factory()->create(['jjb' => 3456]);

        $round = BaccaratRound::forceCreate([
            'status' => 'betting',
            'bet_opens_at' => now(),
            'bet_closes_at' => now()->addMinutes(1),
            'total_bet_big' => 0,
            'total_bet_small' => 0,
            'total_bet_triple' => 0,
            'bet_count' => 0,
            'bet_count_big' => 0,
            'bet_count_small' => 0,
            'bet_count_triple' => 0,
            'total_payout' => 0,
        ]);

        $response = $this->actingAs($user)->getJson(route('baccarat.current'));

        $response->assertStatus(200);
        $response->assertJsonStructure(['round' => ['id', 'status', 'bet_closes_at']]);
        $this->assertEquals($round->id, $response->json('round.id'));
        $this->assertSame(3456, $response->json('jjb'));
    }

    public function test_can_bet()
    {
        Event::fake();

        /** @var \App\Models\User $user */
        $user = User::factory()->create(['jjb' => 200]);

        $round = BaccaratRound::forceCreate([
            'status' => 'betting',
            'bet_opens_at' => now(),
            'bet_closes_at' => now()->addMinutes(1),
            'total_bet_big' => 0,
            'total_bet_small' => 0,
            'total_bet_triple' => 0,
            'bet_count' => 0,
            'bet_count_big' => 0,
            'bet_count_small' => 0,
            'bet_count_triple' => 0,
            'total_payout' => 0,
        ]);

        $response = $this->actingAs($user)->postJson(route('baccarat.bet'), [
            'round_id' => $round->id,
            'bet_type' => 'big',
            'amount' => 100,
        ]);

        $response->assertStatus(200);
        $response->assertJson(['ok' => true]);

        $this->assertEquals(100, $user->fresh()->jjb);
        $this->assertDatabaseHas('baccarat_bets', [
            'round_id' => $round->id,
            'user_id' => $user->id,
            'bet_type' => 'big',
            'amount' => 100,
        ]);

        Event::assertDispatched(\App\Events\BaccaratPoolUpdated::class);
    }

    public function test_cannot_bet_out_of_range()
    {
        /** @var \App\Models\User $user */
        $user = User::factory()->create(['jjb' => 200]);

        $round = BaccaratRound::forceCreate([
            'status' => 'betting',
            'bet_opens_at' => now(),
            'bet_closes_at' => now()->addMinutes(1),
            'total_bet_big' => 0,
            'total_bet_small' => 0,
            'total_bet_triple' => 0,
            'bet_count' => 0,
            'bet_count_big' => 0,
            'bet_count_small' => 0,
            'bet_count_triple' => 0,
            'total_payout' => 0,
        ]);

        $response = $this->actingAs($user)->postJson(route('baccarat.bet'), [
            'round_id' => $round->id,
            'bet_type' => 'big',
            'amount' => 50, // Less than min_bet
        ]);

        $response->assertStatus(200);
        $response->assertJson(['ok' => false]);
    }

    public function test_cannot_bet_twice_in_same_round()
    {
        /** @var \App\Models\User $user */
        $user = User::factory()->create(['jjb' => 200]);

        $round = BaccaratRound::forceCreate([
            'status' => 'betting',
            'bet_opens_at' => now(),
            'bet_closes_at' => now()->addMinutes(1),
            'total_bet_big' => 0,
            'total_bet_small' => 0,
            'total_bet_triple' => 0,
            'bet_count' => 0,
            'bet_count_big' => 0,
            'bet_count_small' => 0,
            'bet_count_triple' => 0,
            'total_payout' => 0,
        ]);

        BaccaratBet::forceCreate([
            'round_id' => $round->id,
            'user_id' => $user->id,
            'bet_type' => 'big',
            'amount' => 100,
            'status' => 'pending',
        ]);

        $response = $this->actingAs($user)->postJson(route('baccarat.bet'), [
            'round_id' => $round->id,
            'bet_type' => 'small',
            'amount' => 100,
        ]);

        $response->assertStatus(200);
        $response->assertJson(['ok' => false]);
    }

    public function test_can_get_history()
    {
        /** @var \App\Models\User $user */
        $user = User::factory()->create();

        BaccaratRound::forceCreate([
            'status' => 'settled',
            'bet_opens_at' => now()->subMinutes(2),
            'bet_closes_at' => now()->subMinutes(1),
            'settled_at' => now(),
            'dice1' => 1,
            'dice2' => 2,
            'dice3' => 3,
            'total_points' => 6,
            'result' => 'small',
            'total_bet_big' => 0,
            'total_bet_small' => 0,
            'total_bet_triple' => 0,
            'bet_count' => 0,
            'bet_count_big' => 0,
            'bet_count_small' => 0,
            'bet_count_triple' => 0,
            'total_payout' => 0,
        ]);

        $response = $this->actingAs($user)->getJson(route('baccarat.history'));

        $response->assertStatus(200);
        $response->assertJsonStructure(['history']);
        $this->assertCount(1, $response->json('history'));
    }

    /**
     * 测试百家乐开奖后会给参与用户发送带右下角提示的私聊结算通知。
     */
    public function test_settlement_pushes_private_toast_notification_to_participant(): void
    {
        Queue::fake();

        GameConfig::updateOrCreate(
            ['game_key' => 'baccarat'],
            [
                'name' => 'Baccarat',
                'icon' => 'baccarat',
                'description' => 'Baccarat Game',
                'enabled' => true,
                // 将 3~18 全部设为庄家收割，确保本测试稳定命中亏损分支。
                'params' => [
                    'min_bet' => 100,
                    'max_bet' => 50000,
                    'kill_points' => range(3, 18),
                ],
            ]
        );

        /** @var \App\Models\User $user */
        $user = User::factory()->create(['jjb' => 400]);

        $round = BaccaratRound::forceCreate([
            'status' => 'betting',
            'bet_opens_at' => now()->subMinute(),
            'bet_closes_at' => now()->subSecond(),
            'total_bet_big' => 100,
            'total_bet_small' => 0,
            'total_bet_triple' => 0,
            'bet_count' => 1,
            'bet_count_big' => 1,
            'bet_count_small' => 0,
            'bet_count_triple' => 0,
            'total_payout' => 0,
        ]);

        BaccaratBet::forceCreate([
            'round_id' => $round->id,
            'user_id' => $user->id,
            'bet_type' => 'big',
            'amount' => 100,
            'payout' => 0,
            'status' => 'pending',
        ]);

        (new CloseBaccaratRoundJob($round))->handle(
            app(\App\Services\UserCurrencyService::class),
            app(\App\Services\ChatStateService::class),
            app(\App\Services\BaccaratLossCoverService::class),
        );

        $messages = Redis::lrange('room:1:messages', 0, -1);
        $privateMessage = collect($messages)
            ->map(fn (string $item) => json_decode($item, true))
            ->first(fn (array $item) => ($item['from_user'] ?? null) === '系统'
                && ($item['to_user'] ?? null) === $user->username
                && ($item['toast_notification']['title'] ?? null) === '🎲 百家乐本局结算');

        $this->assertNotNull($privateMessage);
        $this->assertTrue((bool) ($privateMessage['is_secret'] ?? false));
        $this->assertStringContainsString('净输 100 金币', (string) ($privateMessage['content'] ?? ''));
        $this->assertStringContainsString('-100', (string) ($privateMessage['toast_notification']['message'] ?? ''));
        $this->assertSame('📉', $privateMessage['toast_notification']['icon'] ?? null);
        $this->assertSame('#ef4444', $privateMessage['toast_notification']['color'] ?? null);

        // 公屏开奖消息 + 参与者私聊通知，都应进入异步落库队列。
        Queue::assertPushed(SaveMessageJob::class, 2);
    }
}
