<?php

/**
 * 文件功能：猜成语控制器与后台参数测试
 *
 * 覆盖猜成语后台过期时间配置、手动出题清理超时回合、
 * 以及超时后禁止继续答题的关键行为。
 */

namespace Tests\Feature;

use App\Models\GameConfig;
use App\Models\Riddle;
use App\Models\RiddleGameRound;
use App\Models\Room;
use App\Models\User;
use App\Services\ChatStateService;
use App\Services\RiddleGameService;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Tests\TestCase;

/**
 * 类功能：验证猜成语后台配置与回合过期逻辑。
 */
class RiddleQuizControllerTest extends TestCase
{
    /**
     * 方法功能：为本组测试准备隔离表结构，不触碰本地业务数据库。
     */
    protected function setUp(): void
    {
        parent::setUp();

        $this->rebuildTestingTables();
    }

    /**
     * 方法功能：验证后台可以保存题目过期时间配置。
     */
    public function test_service_uses_legacy_idiom_config_for_brain_teaser_scope_and_interval(): void
    {
        $room = Room::create(['room_name' => '测试房间']);

        GameConfig::create([
            'game_key' => 'idiom',
            'name' => '猜谜活动',
            'icon' => '🧩',
            'enabled' => true,
            'params' => [
                'reward_gold' => 88,
                'reward_exp' => 66,
                'auto_start_interval' => 9,
                'expire_minutes' => 7,
                'room_scope_mode' => 'single',
                'room_ids' => [$room->id],
            ],
        ]);

        /** @var RiddleGameService $service */
        $service = $this->app->make(RiddleGameService::class);

        $this->assertSame(9, $service->getAutoStartInterval(Riddle::TYPE_BRAIN_TEASER));
        $this->assertSame(7, $service->getExpireMinutes(Riddle::TYPE_BRAIN_TEASER));
        $this->assertSame([$room->id], $service->getScopedRoomIds(Riddle::TYPE_BRAIN_TEASER));
    }

    /**
     * 方法功能：验证 single 房间范围模式只取配置中的首个房间。
     */
    public function test_service_only_uses_first_room_when_scope_mode_is_single(): void
    {
        $roomOne = Room::create(['room_name' => '测试房间1']);
        $roomTwo = Room::create(['room_name' => '测试房间2']);

        GameConfig::create([
            'game_key' => 'idiom',
            'name' => '猜谜活动',
            'icon' => '🧩',
            'enabled' => true,
            'params' => [
                'reward_gold' => 50,
                'reward_exp' => 30,
                'auto_start_interval' => 3,
                'expire_minutes' => 5,
                'room_scope_mode' => 'single',
                'room_ids' => [$roomOne->id, $roomTwo->id],
            ],
        ]);

        /** @var RiddleGameService $service */
        $service = $this->app->make(RiddleGameService::class);

        $this->assertSame([$roomOne->id], $service->getScopedRoomIds(Riddle::TYPE_IDIOM));
    }

    /**
     * 方法功能：验证手动出题前会清理已超时的旧回合，避免阻塞新题。
     */
    public function test_start_clears_expired_round_before_creating_new_round(): void
    {
        $this->mockChatStateService();

        $admin = $this->createSiteOwner();
        $room = Room::create(['room_name' => '猜成语房间']);
        $idiom = $this->createActiveIdiom();

        GameConfig::create([
            'game_key' => 'idiom',
            'name' => '猜成语',
            'icon' => '🧩',
            'enabled' => true,
            'params' => [
                'reward_gold' => 50,
                'reward_exp' => 30,
                'auto_start_interval' => 0,
                'expire_minutes' => 5,
                'room_scope_mode' => 'single',
                'room_ids' => [$room->id],
            ],
        ]);

        $expiredRound = RiddleGameRound::create([
            'room_id' => $room->id,
            'idiom_id' => $idiom->id,
            'quiz_type' => Riddle::TYPE_IDIOM,
            'status' => 'active',
            'reward_gold' => 50,
            'reward_exp' => 30,
            'started_at' => now()->subMinutes(10),
        ]);

        $response = $this->actingAs($admin)->postJson(route('riddle-quiz.start'), [
            'room_id' => $room->id,
        ]);

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

        $expiredRound->refresh();

        $this->assertSame('ended', $expiredRound->status);
        $this->assertNotNull($expiredRound->ended_at);
        $this->assertDatabaseCount('idiom_game_rounds', 2);
        $this->assertDatabaseHas('idiom_game_rounds', [
            'room_id' => $room->id,
            'quiz_type' => Riddle::TYPE_IDIOM,
            'status' => 'active',
        ]);
    }

    /**
     * 方法功能：验证旧路由在传入脑筋急转弯题型时也能正常手动开题。
     */
    public function test_start_supports_brain_teaser_quiz_type_on_legacy_route(): void
    {
        $this->mockChatStateService();

        $admin = $this->createSiteOwner();
        $room = Room::create(['room_name' => '猜谜房间']);
        $brainTeaser = $this->createQuestion(
            type: Riddle::TYPE_BRAIN_TEASER,
            answer: '影子',
            hint: '🧠 天天跟着你走，白天有晚上无，看得见摸不着，是什么？',
        );

        GameConfig::create([
            'game_key' => 'idiom',
            'name' => '猜谜活动',
            'icon' => '🧩',
            'enabled' => true,
            'params' => [
                'reward_gold' => 66,
                'reward_exp' => 44,
                'auto_start_interval' => 0,
                'expire_minutes' => 5,
                'room_scope_mode' => 'single',
                'room_ids' => [$room->id],
            ],
        ]);

        $response = $this->actingAs($admin)->postJson(route('riddle-quiz.start'), [
            'room_id' => $room->id,
            'quiz_type' => Riddle::TYPE_BRAIN_TEASER,
        ]);

        $response->assertOk()
            ->assertJsonPath('status', 'success')
            ->assertJsonPath('data.quiz_type', Riddle::TYPE_BRAIN_TEASER)
            ->assertJsonPath('data.hint', $brainTeaser->hint);

        $this->assertDatabaseHas('idiom_game_rounds', [
            'room_id' => $room->id,
            'idiom_id' => $brainTeaser->id,
            'quiz_type' => Riddle::TYPE_BRAIN_TEASER,
            'status' => 'active',
        ]);
    }

    /**
     * 方法功能：验证手动出题会结束当前同题型旧回合并直接开启新题。
     */
    public function test_start_manually_ends_previous_active_round_of_same_quiz_type(): void
    {
        $this->mockChatStateService();

        $admin = $this->createSiteOwner();
        $room = Room::create(['room_name' => '手动出题房间']);
        $questionOne = $this->createQuestion(
            type: Riddle::TYPE_BRAIN_TEASER,
            answer: '影子',
            hint: '🧠 天天跟着你走，白天有晚上无，看得见摸不着，是什么？',
        );
        $questionTwo = $this->createQuestion(
            type: Riddle::TYPE_BRAIN_TEASER,
            answer: '回声',
            hint: '🧠 你喊它也喊，你停它就停，山谷里最常见，是什么？',
        );

        GameConfig::create([
            'game_key' => 'idiom',
            'name' => '猜谜活动',
            'icon' => '🧩',
            'enabled' => true,
            'params' => [
                'reward_gold' => 66,
                'reward_exp' => 44,
                'auto_start_interval' => 0,
                'expire_minutes' => 5,
                'room_scope_mode' => 'single',
                'room_ids' => [$room->id],
            ],
        ]);

        $oldRound = RiddleGameRound::create([
            'room_id' => $room->id,
            'idiom_id' => $questionOne->id,
            'quiz_type' => Riddle::TYPE_BRAIN_TEASER,
            'status' => 'active',
            'reward_gold' => 66,
            'reward_exp' => 44,
            'started_at' => now()->subMinute(),
        ]);

        $response = $this->actingAs($admin)->postJson(route('riddle-quiz.start'), [
            'room_id' => $room->id,
            'quiz_type' => Riddle::TYPE_BRAIN_TEASER,
        ]);

        $response->assertOk()
            ->assertJsonPath('status', 'success')
            ->assertJsonPath('data.quiz_type', Riddle::TYPE_BRAIN_TEASER);

        $oldRound->refresh();

        $this->assertSame('ended', $oldRound->status);
        $this->assertNotNull($oldRound->ended_at);
        $this->assertDatabaseHas('idiom_game_rounds', [
            'room_id' => $room->id,
            'quiz_type' => Riddle::TYPE_BRAIN_TEASER,
            'status' => 'active',
        ]);

        $this->assertSame(2, RiddleGameRound::query()->where('room_id', $room->id)->count());
        $this->assertSame(1, RiddleGameRound::query()
            ->where('room_id', $room->id)
            ->where('quiz_type', Riddle::TYPE_BRAIN_TEASER)
            ->where('status', 'active')
            ->count());
    }

    /**
     * 方法功能：验证游戏总开关关闭时返回明确提示，而不是误报题库为空。
     */
    public function test_start_returns_clear_message_when_riddle_activity_is_disabled(): void
    {
        $this->mockChatStateService();

        $admin = $this->createSiteOwner();
        $room = Room::create(['room_name' => '猜谜房间']);
        $this->createQuestion(
            type: Riddle::TYPE_BRAIN_TEASER,
            answer: '影子',
            hint: '🧠 天天跟着你走，白天有晚上无，看得见摸不着，是什么？',
        );

        GameConfig::create([
            'game_key' => 'idiom',
            'name' => '猜谜活动',
            'icon' => '🧩',
            'enabled' => false,
            'params' => [
                'reward_gold' => 66,
                'reward_exp' => 44,
                'auto_start_interval' => 0,
                'expire_minutes' => 5,
                'room_scope_mode' => 'single',
                'room_ids' => [$room->id],
            ],
        ]);

        $response = $this->actingAs($admin)->postJson(route('riddle-quiz.start'), [
            'room_id' => $room->id,
            'quiz_type' => Riddle::TYPE_BRAIN_TEASER,
        ]);

        $response->assertStatus(400)
            ->assertJsonPath('status', 'error')
            ->assertJsonPath('message', '猜谜活动未开启，请先到游戏管理中开启后再出题。');
    }

    /**
     * 方法功能：验证回合超时后不能继续答题，也不会发放奖励。
     */
    public function test_answer_rejects_expired_round_and_marks_it_ended(): void
    {
        $this->mockChatStateService();

        $player = User::factory()->create([
            'username' => '答题用户',
            'exp_num' => 10,
        ]);
        $room = Room::create(['room_name' => '答题房间']);
        $idiom = $this->createActiveIdiom();

        GameConfig::create([
            'game_key' => 'idiom',
            'name' => '猜成语',
            'icon' => '🧩',
            'enabled' => true,
            'params' => [
                'reward_gold' => 20,
                'reward_exp' => 15,
                'auto_start_interval' => 0,
                'expire_minutes' => 5,
                'room_scope_mode' => 'single',
                'room_ids' => [$room->id],
            ],
        ]);

        $round = RiddleGameRound::create([
            'room_id' => $room->id,
            'idiom_id' => $idiom->id,
            'quiz_type' => Riddle::TYPE_IDIOM,
            'status' => 'active',
            'reward_gold' => 20,
            'reward_exp' => 15,
            'started_at' => now()->subMinutes(6),
        ]);

        $response = $this->actingAs($player)->postJson(route('riddle-quiz.answer'), [
            'round_id' => $round->id,
            'room_id' => $room->id,
            'quiz_type' => Riddle::TYPE_IDIOM,
            'answer' => '画蛇添足',
        ]);

        $response->assertStatus(400)
            ->assertJsonPath('status', 'error')
            ->assertJsonPath('message', '该回合已超时结束');

        $round->refresh();
        $player->refresh();

        $this->assertSame('ended', $round->status);
        $this->assertNotNull($round->ended_at);
        $this->assertNull($round->winner_id);
        $this->assertSame(10, $player->exp_num);
    }

    /**
     * 方法功能：验证同一房间两种题型同时存在时，答对一题不会结算另一题。
     */
    public function test_answer_only_ends_matching_round_when_two_quiz_types_are_active(): void
    {
        $this->mockChatStateService();

        $player = User::factory()->create([
            'username' => '破题用户',
            'exp_num' => 10,
        ]);
        $room = Room::create(['room_name' => '双题房间']);
        $idiom = $this->createQuestion(
            type: Riddle::TYPE_IDIOM,
            answer: '画蛇添足',
            hint: '🧩 四人比赛画蛇，最慢的那个反而多此一举。猜一成语',
        );
        $brainTeaser = $this->createQuestion(
            type: Riddle::TYPE_BRAIN_TEASER,
            answer: '影子',
            hint: '🧠 天天跟着你走，白天有晚上无，看得见摸不着，是什么？',
        );

        GameConfig::create([
            'game_key' => 'idiom',
            'name' => '猜谜活动',
            'icon' => '🧩',
            'enabled' => true,
            'params' => [
                'reward_gold' => 20,
                'reward_exp' => 15,
                'auto_start_interval' => 0,
                'expire_minutes' => 5,
                'room_scope_mode' => 'single',
                'room_ids' => [$room->id],
            ],
        ]);

        $idiomRound = RiddleGameRound::create([
            'room_id' => $room->id,
            'idiom_id' => $idiom->id,
            'quiz_type' => Riddle::TYPE_IDIOM,
            'status' => 'active',
            'reward_gold' => 20,
            'reward_exp' => 15,
            'started_at' => now(),
        ]);
        $brainTeaserRound = RiddleGameRound::create([
            'room_id' => $room->id,
            'idiom_id' => $brainTeaser->id,
            'quiz_type' => Riddle::TYPE_BRAIN_TEASER,
            'status' => 'active',
            'reward_gold' => 20,
            'reward_exp' => 15,
            'started_at' => now(),
        ]);

        $response = $this->actingAs($player)->postJson(route('riddle-quiz.answer'), [
            'round_id' => $idiomRound->id,
            'room_id' => $room->id,
            'quiz_type' => Riddle::TYPE_IDIOM,
            'answer' => '画蛇添足',
        ]);

        $response->assertOk()
            ->assertJsonPath('status', 'success')
            ->assertJsonPath('data.quiz_round_id', $idiomRound->id);

        $idiomRound->refresh();
        $brainTeaserRound->refresh();

        $this->assertSame('answered', $idiomRound->status);
        $this->assertSame('破题用户', $idiomRound->winner_username);
        $this->assertSame('active', $brainTeaserRound->status);
        $this->assertNull($brainTeaserRound->winner_username);
    }

    /**
     * 方法功能：验证自动出题会按房间和题型两个维度独立判断。
     */
    public function test_auto_start_checks_room_and_quiz_type_independently(): void
    {
        $this->mockChatStateService();

        $roomOne = Room::create(['room_name' => '一号房']);
        $roomTwo = Room::create(['room_name' => '二号房']);
        $idiom = $this->createQuestion(
            type: Riddle::TYPE_IDIOM,
            answer: '画蛇添足',
            hint: '🧩 四人比赛画蛇，最慢的那个反而多此一举。猜一成语',
        );
        $brainTeaser = $this->createQuestion(
            type: Riddle::TYPE_BRAIN_TEASER,
            answer: '影子',
            hint: '🧠 天天跟着你走，白天有晚上无，看得见摸不着，是什么？',
        );

        GameConfig::create([
            'game_key' => 'idiom',
            'name' => '猜谜活动',
            'icon' => '🧩',
            'enabled' => true,
            'params' => [
                'reward_gold' => 30,
                'reward_exp' => 18,
                'auto_start_interval' => 1,
                'expire_minutes' => 5,
                'room_scope_mode' => 'multiple',
                'room_ids' => [$roomOne->id, $roomTwo->id],
            ],
        ]);

        // 一号房已有进行中的成语题，只允许系统补开脑筋急转弯题。
        RiddleGameRound::create([
            'room_id' => $roomOne->id,
            'idiom_id' => $idiom->id,
            'quiz_type' => Riddle::TYPE_IDIOM,
            'status' => 'active',
            'reward_gold' => 30,
            'reward_exp' => 18,
            'started_at' => now()->subSeconds(30),
        ]);

        /** @var RiddleGameService $service */
        $service = $this->app->make(RiddleGameService::class);
        $startedCount = $service->autoStartEligibleRounds();

        $this->assertSame(3, $startedCount);
        $this->assertDatabaseHas('idiom_game_rounds', [
            'room_id' => $roomOne->id,
            'quiz_type' => Riddle::TYPE_IDIOM,
            'idiom_id' => $idiom->id,
            'status' => 'active',
        ]);
        $this->assertDatabaseHas('idiom_game_rounds', [
            'room_id' => $roomOne->id,
            'quiz_type' => Riddle::TYPE_BRAIN_TEASER,
            'idiom_id' => $brainTeaser->id,
            'status' => 'active',
        ]);
        $this->assertDatabaseHas('idiom_game_rounds', [
            'room_id' => $roomTwo->id,
            'quiz_type' => Riddle::TYPE_IDIOM,
            'idiom_id' => $idiom->id,
            'status' => 'active',
        ]);
        $this->assertDatabaseHas('idiom_game_rounds', [
            'room_id' => $roomTwo->id,
            'quiz_type' => Riddle::TYPE_BRAIN_TEASER,
            'idiom_id' => $brainTeaser->id,
            'status' => 'active',
        ]);
    }

    /**
     * 方法功能：创建站长账号，满足后台权限与手动出题权限要求。
     */
    private function createSiteOwner(): User
    {
        return User::factory()->create([
            'id' => 1,
            'user_level' => 100,
            'username' => '站长',
        ]);
    }

    /**
     * 方法功能：创建一条可用于猜成语测试的启用题目。
     */
    private function createActiveIdiom(): Riddle
    {
        return $this->createQuestion(
            type: Riddle::TYPE_IDIOM,
            answer: '画蛇添足',
            hint: '🧩 四人比赛画蛇，最慢的那个反而多此一举。猜一成语',
        );
    }

    /**
     * 方法功能：按指定题型创建一条启用中的测试题目。
     */
    private function createQuestion(string $type, string $answer, string $hint): Riddle
    {
        return Riddle::create([
            'type' => $type,
            'answer' => $answer,
            'hint' => $hint,
            'sort' => 1,
            'is_active' => true,
        ]);
    }

    /**
     * 方法功能：重建本组测试所需的最小表结构。
     */
    private function rebuildTestingTables(): void
    {
        Schema::disableForeignKeyConstraints();
        Schema::dropIfExists('idiom_game_rounds');
        Schema::dropIfExists('idioms');
        Schema::dropIfExists('user_currency_logs');
        Schema::dropIfExists('game_configs');
        Schema::dropIfExists('rooms');
        Schema::dropIfExists('users');
        Schema::enableForeignKeyConstraints();

        Schema::create('users', function (Blueprint $table): void {
            $table->id();
            $table->string('username')->nullable();
            $table->string('email')->nullable();
            $table->string('password')->nullable();
            $table->string('remember_token', 100)->nullable();
            $table->unsignedTinyInteger('sex')->default(1);
            $table->text('custom_join_message')->nullable();
            $table->text('custom_leave_message')->nullable();
            $table->integer('user_level')->default(1);
            $table->integer('exp_num')->default(0);
            $table->integer('jjb')->default(0);
            $table->integer('meili')->default(0);
            $table->timestamps();
        });

        Schema::create('rooms', function (Blueprint $table): void {
            $table->id();
            $table->string('room_name')->nullable();
            $table->string('room_owner')->nullable();
            $table->timestamps();
        });

        Schema::create('game_configs', function (Blueprint $table): void {
            $table->id();
            $table->string('game_key')->unique();
            $table->string('name')->nullable();
            $table->string('icon')->nullable();
            $table->text('description')->nullable();
            $table->boolean('enabled')->default(false);
            $table->json('params')->nullable();
            $table->timestamps();
        });

        Schema::create('idioms', function (Blueprint $table): void {
            $table->id();
            $table->string('type', 30)->default(Riddle::TYPE_IDIOM);
            $table->string('answer', 50);
            $table->string('hint', 255);
            $table->boolean('is_active')->default(true);
            $table->integer('sort')->default(0);
            $table->timestamps();
        });

        Schema::create('idiom_game_rounds', function (Blueprint $table): void {
            $table->id();
            $table->unsignedBigInteger('room_id');
            $table->unsignedBigInteger('idiom_id');
            $table->string('quiz_type', 30)->default(Riddle::TYPE_IDIOM);
            $table->string('status', 20)->default('pending');
            $table->integer('reward_gold')->default(0);
            $table->integer('reward_exp')->default(0);
            $table->unsignedBigInteger('winner_id')->nullable();
            $table->string('winner_username', 50)->nullable();
            $table->timestamp('started_at')->nullable();
            $table->timestamp('ended_at')->nullable();
            $table->timestamps();
        });

        Schema::create('user_currency_logs', function (Blueprint $table): void {
            $table->id();
            $table->unsignedBigInteger('user_id')->index();
            $table->string('username', 50);
            $table->string('currency', 10);
            $table->integer('amount');
            $table->integer('balance_after');
            $table->string('source', 30)->index();
            $table->string('remark', 200)->nullable();
            $table->unsignedBigInteger('room_id')->nullable();
            $table->timestamp('created_at')->nullable()->index();
        });
    }

    /**
     * 方法功能：替换聊天室状态服务，避免测试依赖真实 Redis。
     */
    private function mockChatStateService(): void
    {
        $chatStateService = \Mockery::mock(ChatStateService::class);
        $chatStateService->shouldReceive('nextMessageId')->andReturn(1);
        $chatStateService->shouldReceive('pushMessage')->andReturnNull();

        $this->app->instance(ChatStateService::class, $chatStateService);
    }
}
