<?php

use App\Models\AgentProfile;
use App\Models\Player;
use App\Models\AuditLog;
use App\Models\AdminRole;
use App\Models\AdminUser;
use App\Models\PlayerWallet;
use App\Support\PlayerAuthSource;
use App\Support\PlayerFundingMode;
use Database\Seeders\CurrencySeeder;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\DB;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->seed(CurrencySeeder::class);
    $this->artisan('lottery:admin-auth-sync')->assertExitCode(0);
});

function playerManageAdminToken(): string
{
    $admin = AdminUser::query()->create([
        'username' => 'player_manage_admin',
        'name' => 'Player Manage Admin',
        'email' => null,
        'password' => Hash::make('secret-strong'),
        'status' => 0,
    ]);
    grantSuperAdminRole($admin);

    return $admin->createToken('test', ['*'], now()->addDay())->plainTextToken;
}

function playerPermissionAdminToken(string $username, array $permissionSlugs): string
{
    $admin = AdminUser::query()->create([
        'username' => $username,
        'name' => 'Player Permission Admin',
        'email' => null,
        'password' => Hash::make('secret-strong'),
        'status' => 0,
    ]);

    $role = AdminRole::query()->create([
        'slug' => 'role_'.$username,
        'name' => 'Role '.$username,
    ]);
    $role->syncLegacyPermissionSlugs($permissionSlugs);

    $admin->roles()->sync([
        (int) $role->id => [
            'site_id' => AdminUser::defaultAdminSiteId(),
            'granted_at' => now(),
        ],
    ]);

    return $admin->createToken('test', ['*'], now()->addDay())->plainTextToken;
}

function playerPermissionRequest($test, string $token)
{
    app('auth')->forgetGuards();

    return $test->withHeader('Authorization', 'Bearer '.$token);
}

test('super admin can create player under site root agent', function (): void {
    $siteCode = DB::table('admin_sites')->where('is_default', true)->value('code');
    $siteCode = is_string($siteCode) && $siteCode !== '' ? $siteCode : 'default_site';

    $token = playerManageAdminToken();

    $sitePlayerId = 'manual-create-'.uniqid('', true);

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->postJson('/api/v1/admin/players', [
            'site_code' => $siteCode,
            'site_player_id' => $sitePlayerId,
            'default_currency' => 'NPR',
            'status' => 0,
        ])
        ->assertCreated()
        ->assertJsonPath('data.site_code', $siteCode)
        ->assertJsonPath('data.site_player_id', $sitePlayerId);

    $this->assertDatabaseHas('players', [
        'site_code' => $siteCode,
        'site_player_id' => $sitePlayerId,
    ]);
});

test('platform user without agent binding cannot create player', function (): void {
    $siteCode = DB::table('admin_sites')->where('is_default', true)->value('code');
    $siteCode = is_string($siteCode) && $siteCode !== '' ? $siteCode : 'default_site';

    $token = playerPermissionAdminToken('player_manager_no_agent', ['prd.users.manage']);

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->postJson('/api/v1/admin/players', [
            'site_code' => $siteCode,
            'site_player_id' => 'blocked-create',
            'default_currency' => 'NPR',
        ])
        ->assertStatus(422)
        ->assertJsonPath('msg', fn (string $msg): bool => str_contains($msg, '代理') || str_contains($msg, 'agent'));
});

test('admin can freeze and unfreeze player with audit log', function (): void {
    $siteCode = DB::table('admin_sites')->where('is_default', true)->value('code');
    $siteCode = is_string($siteCode) && $siteCode !== '' ? $siteCode : 'default_site';

    $player = Player::query()->create([
        'site_code' => $siteCode,
        'site_player_id' => 'freeze-1',
        'username' => 'freeze_user',
        'nickname' => 'Freeze',
        'default_currency' => 'NPR',
        'status' => 0,
    ]);

    PlayerWallet::query()->create([
        'player_id' => $player->id,
        'wallet_type' => 'lottery',
        'currency_code' => 'NPR',
        'balance' => 1_000,
        'frozen_balance' => 0,
        'status' => 0,
        'version' => 0,
    ]);

    $token = playerManageAdminToken();

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->postJson('/api/v1/admin/players/'.$player->id.'/freeze')
        ->assertOk()
        ->assertJsonPath('data.status', 1);

    $this->assertDatabaseHas('audit_logs', [
        'module_code' => 'player_manage',
        'action_code' => 'freeze',
        'target_type' => 'player',
        'target_id' => (string) $player->id,
    ]);

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->postJson('/api/v1/admin/players/'.$player->id.'/unfreeze')
        ->assertOk()
        ->assertJsonPath('data.status', 0);

    expect(AuditLog::query()->where('module_code', 'player_manage')->count())->toBe(2);
});

test('player manage permission gates write and freeze APIs separately from view permissions', function (): void {
    $siteCode = DB::table('admin_sites')->where('is_default', true)->value('code');
    $siteCode = is_string($siteCode) && $siteCode !== '' ? $siteCode : 'default_site';

    $player = Player::query()->create([
        'site_code' => $siteCode,
        'site_player_id' => 'perm-1',
        'username' => 'perm_user',
        'nickname' => 'Perm',
        'default_currency' => 'NPR',
        'status' => 0,
    ]);

    $financeToken = playerPermissionAdminToken('player_finance_viewer', ['prd.users.view_finance']);

    playerPermissionRequest($this, $financeToken)
        ->getJson('/api/v1/admin/players?per_page=10')
        ->assertOk();

    playerPermissionRequest($this, $financeToken)
        ->getJson('/api/v1/admin/players/'.$player->id.'/wallets')
        ->assertOk();

    playerPermissionRequest($this, $financeToken)
        ->getJson('/api/v1/admin/players/'.$player->id.'/ticket-items')
        ->assertForbidden();

    playerPermissionRequest($this, $financeToken)
        ->postJson('/api/v1/admin/players', [
            'site_code' => 'main',
            'site_player_id' => 'created-by-finance',
            'default_currency' => 'NPR',
        ])
        ->assertForbidden();

    playerPermissionRequest($this, $financeToken)
        ->putJson('/api/v1/admin/players/'.$player->id, ['nickname' => 'blocked'])
        ->assertForbidden();

    playerPermissionRequest($this, $financeToken)
        ->postJson('/api/v1/admin/players/'.$player->id.'/freeze')
        ->assertForbidden();

    $csToken = playerPermissionAdminToken('player_cs_viewer', ['prd.users.view_cs']);

    playerPermissionRequest($this, $csToken)
        ->getJson('/api/v1/admin/players?per_page=10')
        ->assertOk();

    playerPermissionRequest($this, $csToken)
        ->getJson('/api/v1/admin/players/'.$player->id.'/ticket-items')
        ->assertOk();

    playerPermissionRequest($this, $csToken)
        ->getJson('/api/v1/admin/players/'.$player->id.'/wallets')
        ->assertForbidden();

    $freezeToken = playerPermissionAdminToken('player_freezer', ['prd.player_freeze.manage']);

    playerPermissionRequest($this, $freezeToken)
        ->getJson('/api/v1/admin/players?per_page=10')
        ->assertForbidden();

    $freezeResp = playerPermissionRequest($this, $freezeToken)
        ->postJson('/api/v1/admin/players/'.$player->id.'/freeze');
    $freezeResp->assertOk()
        ->assertJsonPath('data.status', 1);

    playerPermissionRequest($this, $freezeToken)
        ->postJson('/api/v1/admin/players/'.$player->id.'/unfreeze')
        ->assertOk()
        ->assertJsonPath('data.status', 0);

    $manageToken = playerPermissionAdminToken('player_manager', ['prd.users.manage']);

    playerPermissionRequest($this, $manageToken)
        ->getJson('/api/v1/admin/players/'.$player->id.'/wallets')
        ->assertOk();

    playerPermissionRequest($this, $manageToken)
        ->getJson('/api/v1/admin/players/'.$player->id.'/ticket-items')
        ->assertOk();
});

test('admin can update player default currency and validation rejects unknown code', function (): void {
    $player = Player::query()->create([
        'site_code' => 'main',
        'site_player_id' => 'currency-1',
        'username' => 'currency_user',
        'nickname' => 'Currency',
        'default_currency' => 'NPR',
        'status' => 0,
    ]);

    $token = playerManageAdminToken();

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->putJson('/api/v1/admin/players/'.$player->id, [
            'default_currency' => 'usd',
        ])
        ->assertOk()
        ->assertJsonPath('data.default_currency', 'USD');

    $this->assertDatabaseHas('players', [
        'id' => $player->id,
        'default_currency' => 'USD',
    ]);

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->putJson('/api/v1/admin/players/'.$player->id, [
            'default_currency' => 'ABC',
        ])
        ->assertStatus(422);
});

test('admin can set player credit limit without clobbering used credit', function (): void {
    $siteCode = DB::table('admin_sites')->where('is_default', true)->value('code');
    $siteCode = is_string($siteCode) && $siteCode !== '' ? $siteCode : 'default_site';
    $rootId = (int) DB::table('agent_nodes')->where('depth', 0)->value('id');

    AgentProfile::query()->updateOrCreate(
        ['agent_node_id' => $rootId],
        [
            'total_share_rate' => 60,
            'credit_limit' => 50_000,
            'allocated_credit' => 0,
            'used_credit' => 0,
            'rebate_limit' => 0.01,
            'default_player_rebate' => 0.005,
        ],
    );

    $player = Player::query()->create([
        'site_code' => $siteCode,
        'agent_node_id' => $rootId,
        'site_player_id' => 'credit-limit-1',
        'auth_source' => PlayerAuthSource::LOTTERY_NATIVE,
        'funding_mode' => PlayerFundingMode::CREDIT,
        'username' => 'credit_limit_user',
        'default_currency' => 'NPR',
        'status' => 0,
    ]);

    DB::table('player_credit_accounts')->insert([
        'player_id' => $player->id,
        'credit_limit' => 500,
        'used_credit' => 120,
        'frozen_credit' => 0,
        'created_at' => now(),
        'updated_at' => now(),
    ]);

    $token = playerManageAdminToken();

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->putJson('/api/v1/admin/players/'.$player->id, [
            'credit_limit' => 2000,
        ])
        ->assertOk()
        ->assertJsonPath('data.credit_limit', 2000)
        ->assertJsonPath('data.available_credit', 1880);

    $this->assertDatabaseHas('player_credit_accounts', [
        'player_id' => $player->id,
        'credit_limit' => 2000,
        'used_credit' => 120,
    ]);
});

test('wallet player update rejects credit limit and rebate', function (): void {
    $siteCode = DB::table('admin_sites')->where('is_default', true)->value('code');
    $siteCode = is_string($siteCode) && $siteCode !== '' ? $siteCode : 'default_site';

    $player = Player::query()->create([
        'site_code' => $siteCode,
        'site_player_id' => 'wallet-no-credit-1',
        'auth_source' => PlayerAuthSource::MAIN_SITE_SSO,
        'funding_mode' => PlayerFundingMode::WALLET,
        'username' => 'wallet_user',
        'default_currency' => 'NPR',
        'status' => 0,
    ]);

    PlayerWallet::query()->create([
        'player_id' => $player->id,
        'currency_code' => 'NPR',
        'balance_minor' => 0,
    ]);

    $token = playerManageAdminToken();

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->putJson('/api/v1/admin/players/'.$player->id, [
            'credit_limit' => 1000,
        ])
        ->assertStatus(422)
        ->assertJsonPath('code', \App\Lottery\ErrorCode::ValidationFailed->value);

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->putJson('/api/v1/admin/players/'.$player->id, [
            'rebate_rate' => 1,
        ])
        ->assertStatus(422)
        ->assertJsonPath('code', \App\Lottery\ErrorCode::ValidationFailed->value);
});

test('native player create rejects chinese username', function (): void {
    $siteCode = DB::table('admin_sites')->where('is_default', true)->value('code');
    $siteCode = is_string($siteCode) && $siteCode !== '' ? $siteCode : 'default_site';
    $token = playerManageAdminToken();

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->postJson('/api/v1/admin/players', [
            'site_code' => $siteCode,
            'username' => '玩家账号',
            'password' => 'secret-native',
            'default_currency' => 'NPR',
        ])
        ->assertStatus(422)
        ->assertJsonPath('code', \App\Lottery\ErrorCode::ValidationFailed->value);
});

test('partial rebate update preserves the other rebate field', function (): void {
    $siteCode = DB::table('admin_sites')->where('is_default', true)->value('code');
    $siteCode = is_string($siteCode) && $siteCode !== '' ? $siteCode : 'default_site';
    $rootId = (int) DB::table('agent_nodes')->where('depth', 0)->value('id');

    AgentProfile::query()->updateOrCreate(
        ['agent_node_id' => $rootId],
        [
            'total_share_rate' => 100,
            'credit_limit' => 50_000,
            'allocated_credit' => 0,
            'used_credit' => 0,
            'rebate_limit' => 0.02,
            'default_player_rebate' => 0.005,
            'can_grant_extra_rebate' => true,
        ],
    );

    $player = Player::query()->create([
        'site_code' => $siteCode,
        'agent_node_id' => $rootId,
        'site_player_id' => 'partial-rebate-1',
        'auth_source' => PlayerAuthSource::LOTTERY_NATIVE,
        'funding_mode' => PlayerFundingMode::CREDIT,
        'username' => 'partial_rebate_user',
        'default_currency' => 'NPR',
        'status' => 0,
    ]);

    DB::table('player_credit_accounts')->insert([
        'player_id' => $player->id,
        'credit_limit' => 0,
        'used_credit' => 0,
        'frozen_credit' => 0,
        'created_at' => now(),
        'updated_at' => now(),
    ]);

    DB::table('player_rebate_profiles')->insert([
        'player_id' => $player->id,
        'game_type' => '*',
        'inherit_from_agent' => false,
        'rebate_rate' => 0.005,
        'extra_rebate_rate' => 0.002,
        'created_at' => now(),
        'updated_at' => now(),
    ]);

    $token = playerManageAdminToken();

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->putJson('/api/v1/admin/players/'.$player->id, [
            'extra_rebate_rate' => 0.1,
        ])
        ->assertOk();

    $this->assertDatabaseHas('player_rebate_profiles', [
        'player_id' => $player->id,
        'game_type' => '*',
        'rebate_rate' => 0.005,
        'extra_rebate_rate' => 0.001,
    ]);

    DB::table('player_rebate_profiles')
        ->where('player_id', $player->id)
        ->where('game_type', '*')
        ->update([
            'rebate_rate' => 0.008,
            'extra_rebate_rate' => 0.001,
            'updated_at' => now(),
        ]);

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->putJson('/api/v1/admin/players/'.$player->id, [
            'rebate_rate' => 0.3,
        ])
        ->assertOk();

    $this->assertDatabaseHas('player_rebate_profiles', [
        'player_id' => $player->id,
        'game_type' => '*',
        'rebate_rate' => 0.003,
        'extra_rebate_rate' => 0.001,
    ]);
});
