<?php

/**
 * 文件功能：用户成就系统功能测试。
 *
 * 覆盖固定成就扫描、重复扫描幂等性与本人可见通知。
 */

namespace Tests\Feature;

use App\Enums\CurrencySource;
use App\Jobs\SaveMessageJob;
use App\Models\DailySignIn;
use App\Models\Message;
use App\Models\Room;
use App\Models\User;
use App\Models\UserAchievement;
use App\Models\UserCurrencyLog;
use App\Services\AchievementService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Redis;
use Tests\TestCase;

/**
 * 类功能：验证成就扫描服务和 Artisan 命令的核心行为。
 */
class AchievementServiceTest extends TestCase
{
    use RefreshDatabase;

    /**
     * 每个测试前清空 Redis 房间状态。
     */
    protected function setUp(): void
    {
        parent::setUp();
        $this->flushChatRoomRedisState();
    }

    /**
     * 测试命令可以按现有日志解锁聊天、签到与游戏成就。
     */
    public function test_scan_command_unlocks_achievements_from_existing_logs(): void
    {
        $room = Room::create(['room_name' => 'ach']);
        $user = User::factory()->create([
            'username' => 'achiever',
            'room_id' => $room->id,
            'jjb' => 700000,
            'bank_jjb' => 300000,
        ]);

        for ($i = 0; $i < 100; $i++) {
            Message::query()->create([
                'room_id' => $room->id,
                'from_user' => $user->username,
                'to_user' => '大家',
                'content' => '成就聊天',
                'is_secret' => false,
                'font_color' => '',
                'action' => '',
                'message_type' => 'text',
                'retention_type' => Message::RETENTION_USER_CHAT,
                'sent_at' => now()->subMinutes($i + 1),
            ]);
        }

        DailySignIn::query()->create([
            'user_id' => $user->id,
            'room_id' => $room->id,
            'sign_in_date' => today(),
            'streak_days' => 7,
            'gold_reward' => 10,
            'exp_reward' => 20,
            'charm_reward' => 0,
        ]);

        UserCurrencyLog::query()->create([
            'user_id' => $user->id,
            'username' => $user->username,
            'currency' => 'gold',
            'amount' => 1000,
            'balance_after' => 1000,
            'source' => CurrencySource::GAME_REWARD->value,
            'remark' => '猜谜奖励',
            'room_id' => $room->id,
        ]);
        UserCurrencyLog::query()->create([
            'user_id' => $user->id,
            'username' => $user->username,
            'currency' => 'gold',
            'amount' => -1000,
            'balance_after' => 0,
            'source' => CurrencySource::BACCARAT_BET->value,
            'remark' => '百家乐下注',
            'room_id' => $room->id,
        ]);

        $this->artisan('achievements:scan', ['--user' => $user->username])
            ->assertSuccessful();

        $this->assertDatabaseHas('user_achievements', [
            'user_id' => $user->id,
            'achievement_key' => 'chat_first_message',
        ]);
        $this->assertDatabaseHas('user_achievements', [
            'user_id' => $user->id,
            'achievement_key' => 'chat_100_messages',
        ]);
        $this->assertDatabaseHas('user_achievements', [
            'user_id' => $user->id,
            'achievement_key' => 'signin_7_streak',
        ]);
        $this->assertDatabaseHas('user_achievements', [
            'user_id' => $user->id,
            'achievement_key' => 'game_riddle_win',
        ]);
        $this->assertDatabaseHas('user_achievements', [
            'user_id' => $user->id,
            'achievement_key' => 'game_win_1000',
        ]);
        $this->assertDatabaseHas('user_achievements', [
            'user_id' => $user->id,
            'achievement_key' => 'game_loss_1000',
        ]);
        $this->assertDatabaseHas('user_achievements', [
            'user_id' => $user->id,
            'achievement_key' => 'growth_assets_1000000',
        ]);
        $this->assertDatabaseHas('user_achievement_progress', [
            'user_id' => $user->id,
            'achievement_key' => 'chat_100_messages',
            'progress_value' => 100,
            'threshold_value' => 100,
        ]);

        $this->assertNotNull(UserAchievement::query()
            ->where('user_id', $user->id)
            ->where('achievement_key', 'chat_100_messages')
            ->value('achieved_at'));
    }

    /**
     * 测试重复扫描不会重复创建同一个用户成就。
     */
    public function test_scan_command_is_idempotent_for_same_achievement(): void
    {
        $room = Room::create(['room_name' => 'idem']);
        $user = User::factory()->create(['username' => 'idem_user', 'room_id' => $room->id]);
        Message::query()->create([
            'room_id' => $room->id,
            'from_user' => $user->username,
            'to_user' => '大家',
            'content' => '第一条',
            'is_secret' => false,
            'font_color' => '',
            'action' => '',
            'message_type' => 'text',
            'retention_type' => Message::RETENTION_USER_CHAT,
            'sent_at' => now(),
        ]);

        $this->artisan('achievements:scan', ['--user' => $user->id])->assertSuccessful();
        $this->artisan('achievements:scan', ['--user' => $user->id])->assertSuccessful();

        $this->assertSame(1, UserAchievement::query()
            ->where('user_id', $user->id)
            ->where('achievement_key', 'chat_first_message')
            ->count());
    }

    /**
     * 测试成就解锁通知只发给本人并带悄悄话标记。
     */
    public function test_unlock_notification_is_private_to_the_user(): void
    {
        Queue::fake([SaveMessageJob::class]);

        $room = Room::create(['room_name' => 'notice']);
        $user = User::factory()->create(['username' => 'notice_user', 'room_id' => $room->id]);
        Message::query()->create([
            'room_id' => $room->id,
            'from_user' => $user->username,
            'to_user' => '大家',
            'content' => '第一条',
            'is_secret' => false,
            'font_color' => '',
            'action' => '',
            'message_type' => 'text',
            'retention_type' => Message::RETENTION_USER_CHAT,
            'sent_at' => now(),
        ]);

        app(AchievementService::class)->scanUser($user, notify: true);

        $messages = collect(Redis::lrange("room:{$room->id}:messages", 0, -1))
            ->map(fn (string $item): array => json_decode($item, true));
        $notice = $messages->first(fn (array $item): bool => ($item['action'] ?? '') === 'achievement_unlocked');

        $this->assertNotNull($notice);
        $this->assertSame($user->username, $notice['to_user'] ?? null);
        $this->assertTrue((bool) ($notice['is_secret'] ?? false));
        $this->assertSame(Message::RETENTION_SYSTEM_NOTICE, $notice['retention_type'] ?? null);
        Queue::assertPushed(SaveMessageJob::class);
    }

    /**
     * 测试打开我的成就页面时会静默补算已达标成就。
     */
    public function test_achievement_page_silently_unlocks_reached_achievements(): void
    {
        $room = Room::create(['room_name' => 'page']);
        $user = User::factory()->create(['username' => 'page_user', 'room_id' => $room->id]);

        Message::query()->create([
            'room_id' => $room->id,
            'from_user' => $user->username,
            'to_user' => '大家',
            'content' => '页面触发补算',
            'is_secret' => false,
            'font_color' => '',
            'action' => '',
            'message_type' => 'text',
            'retention_type' => Message::RETENTION_USER_CHAT,
            'sent_at' => now(),
        ]);

        $this->assertDatabaseMissing('user_achievements', [
            'user_id' => $user->id,
            'achievement_key' => 'chat_first_message',
        ]);

        $this->actingAs($user)
            ->get(route('achievements.index'))
            ->assertOk()
            ->assertSee('已解锁 1 /', false);

        $this->assertDatabaseHas('user_achievements', [
            'user_id' => $user->id,
            'achievement_key' => 'chat_first_message',
        ]);

        $this->assertNotNull(UserAchievement::query()
            ->where('user_id', $user->id)
            ->where('achievement_key', 'chat_first_message')
            ->value('achieved_at'));
    }

    /**
     * 测试我的成就页面可以按已完成和未达成筛选。
     */
    public function test_achievement_page_can_filter_by_unlocked_and_locked_tabs(): void
    {
        $room = Room::create(['room_name' => 'tabs']);
        $user = User::factory()->create(['username' => 'tab_user', 'room_id' => $room->id]);

        Message::query()->create([
            'room_id' => $room->id,
            'from_user' => $user->username,
            'to_user' => '大家',
            'content' => '筛选测试',
            'is_secret' => false,
            'font_color' => '',
            'action' => '',
            'message_type' => 'text',
            'retention_type' => Message::RETENTION_USER_CHAT,
            'sent_at' => now(),
        ]);

        $this->actingAs($user)
            ->get(route('achievements.index', ['status' => 'unlocked']))
            ->assertOk()
            ->assertSee('已完成')
            ->assertSee('初来乍到')
            ->assertDontSee('百句达人');

        $this->actingAs($user)
            ->get(route('achievements.index', ['status' => 'locked']))
            ->assertOk()
            ->assertSee('未达成')
            ->assertSee('百句达人')
            ->assertDontSee('初来乍到');
    }
}
