<?php

use Firebase\JWT\JWT;
use App\Models\Player;
use App\Lottery\ErrorCode;
use App\Support\PlayerAuthSource;
use App\Support\PlayerFundingMode;
use Illuminate\Support\Facades\DB;
use Database\Seeders\CurrencySeeder;
use Illuminate\Support\Facades\Hash;
use Database\Seeders\LotterySettingsSeeder;
use App\Services\Player\PlayerNativeAuthService;
use App\Exceptions\PlayerAuthenticationException;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    config([
        'lottery.player_auth.native.secret' => 'independent-native-secret-32bytes!!',
        'lottery.player_auth.native.ttl_seconds' => 3600,
        'lottery.main_site.sso_jwt_secret' => null,
        'lottery.main_site.wallet_api_url' => null,
    ]);

    $this->seed(CurrencySeeder::class);
    $this->seed(LotterySettingsSeeder::class);
});

function nativeSecretGuardPlayer(string $suffix): Player
{
    $site = DB::table('admin_sites')->where('is_default', true)->first();
    $rootId = (int) DB::table('agent_nodes')->where('depth', 0)->value('id');

    return Player::query()->create([
        'site_code' => (string) $site->code,
        'agent_node_id' => $rootId,
        'site_player_id' => 'native:secret-guard-'.$suffix,
        'auth_source' => PlayerAuthSource::LOTTERY_NATIVE,
        'funding_mode' => PlayerFundingMode::CREDIT,
        'username' => 'secret_guard_'.$suffix,
        'password_hash' => Hash::make('secret-pass'),
        'default_currency' => 'NPR',
        'status' => 0,
    ]);
}

function nativeSecretGuardToken(Player $player, string $secret): string
{
    $now = time();

    return JWT::encode([
        'player_id' => (int) $player->id,
        'auth_source' => PlayerAuthSource::LOTTERY_NATIVE,
        'token_version' => (int) ($player->native_token_version ?? 0),
        'site_code' => (string) $player->site_code,
        'iat' => $now,
        'exp' => $now + 3600,
    ], $secret, 'HS256');
}

function expectNativeSecretConfigurationRejected(Closure $callback): void
{
    try {
        $callback();
        test()->fail('Expected native JWT secret configuration to be rejected.');
    } catch (PlayerAuthenticationException $exception) {
        expect($exception->lotteryCode)->toBe(ErrorCode::PlayerSsoSecretNotConfigured->value)
            ->and($exception->httpStatus)->toBe(503);
    }
}

test('missing native secret rejects token issuance and verification with 503', function (): void {
    $player = nativeSecretGuardPlayer('missing');
    config(['lottery.player_auth.native.secret' => null]);

    expectNativeSecretConfigurationRejected(
        fn () => app(PlayerNativeAuthService::class)->issueToken($player),
    );

    $token = nativeSecretGuardToken($player, 'previous-native-secret-32bytes!!');
    $this->withHeader('Authorization', 'Bearer '.$token)
        ->getJson('/api/v1/player/me')
        ->assertStatus(503)
        ->assertJsonPath('code', ErrorCode::PlayerSsoSecretNotConfigured->value);
});

test('native secret matching legacy sso secret rejects token issuance and verification', function (): void {
    $player = nativeSecretGuardPlayer('legacy-match');
    $sharedSecret = 'shared-legacy-native-secret-32bytes!!';
    config([
        'lottery.player_auth.native.secret' => $sharedSecret,
        'lottery.main_site.sso_jwt_secret' => $sharedSecret,
    ]);

    expectNativeSecretConfigurationRejected(
        fn () => app(PlayerNativeAuthService::class)->issueToken($player),
    );

    $token = nativeSecretGuardToken($player, $sharedSecret);
    $this->withHeader('Authorization', 'Bearer '.$token)
        ->getJson('/api/v1/player/me')
        ->assertStatus(503)
        ->assertJsonPath('code', ErrorCode::PlayerSsoSecretNotConfigured->value);
});

test('native secret matching enabled database site sso secret rejects issuance and verification', function (): void {
    $player = nativeSecretGuardPlayer('database-match');
    $sharedSecret = 'shared-database-native-secret-32bytes!!';
    config([
        'lottery.player_auth.native.secret' => $sharedSecret,
        'lottery.main_site.sso_jwt_secret' => 'different-legacy-sso-secret-32bytes!!',
    ]);
    DB::table('admin_sites')->where('is_default', true)->update([
        'status' => 1,
        'sso_jwt_secret_encrypted' => encrypt($sharedSecret),
        'updated_at' => now(),
    ]);

    expectNativeSecretConfigurationRejected(
        fn () => app(PlayerNativeAuthService::class)->issueToken($player),
    );

    $token = nativeSecretGuardToken($player, $sharedSecret);
    $this->withHeader('Authorization', 'Bearer '.$token)
        ->getJson('/api/v1/player/me')
        ->assertStatus(503)
        ->assertJsonPath('code', ErrorCode::PlayerSsoSecretNotConfigured->value);
});

test('native secret matching disabled database site sso secret rejects issuance and verification', function (): void {
    $player = nativeSecretGuardPlayer('disabled-database-match');
    $sharedSecret = 'shared-disabled-site-secret-32bytes!!';
    config([
        'lottery.player_auth.native.secret' => $sharedSecret,
        'lottery.main_site.sso_jwt_secret' => 'different-legacy-sso-secret-32bytes!!',
    ]);
    DB::table('admin_sites')->where('is_default', true)->update([
        'status' => 0,
        'sso_jwt_secret_encrypted' => encrypt($sharedSecret),
        'updated_at' => now(),
    ]);

    expectNativeSecretConfigurationRejected(
        fn () => app(PlayerNativeAuthService::class)->issueToken($player),
    );

    $token = nativeSecretGuardToken($player, $sharedSecret);
    $this->withHeader('Authorization', 'Bearer '.$token)
        ->getJson('/api/v1/player/me')
        ->assertStatus(503)
        ->assertJsonPath('code', ErrorCode::PlayerSsoSecretNotConfigured->value);
});

test('independent native secret still issues and verifies tokens', function (): void {
    $player = nativeSecretGuardPlayer('independent');
    config([
        'lottery.player_auth.native.secret' => 'independent-native-secret-32bytes!!',
        'lottery.main_site.sso_jwt_secret' => 'different-legacy-sso-secret-32bytes!!',
    ]);
    DB::table('admin_sites')->where('is_default', true)->update([
        'status' => 1,
        'sso_jwt_secret_encrypted' => encrypt('different-database-sso-secret-32bytes!!'),
        'updated_at' => now(),
    ]);

    $token = app(PlayerNativeAuthService::class)->issueToken($player);

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->getJson('/api/v1/player/me')
        ->assertOk()
        ->assertJsonPath('data.id', $player->id)
        ->assertJsonPath('data.auth_source', PlayerAuthSource::LOTTERY_NATIVE);
});
