<?php

use App\Models\AdminRole;
use App\Models\AdminUser;
use App\Models\LotterySetting;
use App\Services\LotterySettings;
use Illuminate\Support\Facades\Hash;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

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

function settingsAdminToken(): string
{
    $admin = AdminUser::query()->create([
        'username' => 'settings_editor',
        'name' => 'Settings Editor',
        'email' => null,
        'password' => Hash::make('secret-strong'),
        'status' => 0,
    ]);

    $role = AdminRole::query()->create([
        'slug' => 'settings_role',
        'name' => 'Settings Role',
    ]);
    $role->syncLegacyPermissionSlugs([
        'prd.payout.manage',
        'prd.draw_result.manage',
        'prd.wallet_reconcile.manage',
        'prd.rebate.manage',
    ]);

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

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

function settingsReadOnlyToken(): string
{
    $admin = AdminUser::query()->create([
        'username' => 'settings_readonly',
        'name' => 'Settings Readonly',
        'email' => null,
        'password' => Hash::make('secret-strong'),
        'status' => 0,
    ]);

    $role = AdminRole::query()->create([
        'slug' => 'settings_readonly_role',
        'name' => 'Settings Readonly Role',
    ]);
    $role->syncLegacyPermissionSlugs(['prd.rebate.manage']);

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

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

test('admin can batch update settings in one request', function (): void {
    LotterySettings::put('draw.cooldown_minutes', 15, 'draw');
    LotterySettings::put('draw.require_manual_review', true, 'draw');

    $token = settingsAdminToken();

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->putJson('/api/v1/admin/settings/batch', [
            'items' => [
                ['key' => 'draw.cooldown_minutes', 'value' => 20],
                ['key' => 'draw.require_manual_review', 'value' => false],
            ],
        ])
        ->assertOk()
        ->assertJsonPath('data.items.0.key', 'draw.cooldown_minutes');

    expect(LotterySetting::query()->where('setting_key', 'draw.cooldown_minutes')->value('value_json'))->toBe(20)
        ->and(LotterySetting::query()->where('setting_key', 'draw.require_manual_review')->value('value_json'))->toBeFalse();
});

test('admin cannot batch update env locked deployment settings', function (): void {
    config(['lottery.draw.interval_minutes' => 5]);
    LotterySettings::put('draw.interval_minutes', 5, 'draw');

    $token = settingsAdminToken();

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->putJson('/api/v1/admin/settings/batch', [
            'items' => [
                ['key' => 'draw.interval_minutes', 'value' => 10],
            ],
        ])
        ->assertForbidden();

    expect(LotterySettings::drawIntervalMinutes())->toBe(5);
});

test('admin settings deployment returns read only snapshot', function (): void {
    config([
        'lottery.default_currency' => 'USD',
        'lottery.draw.interval_minutes' => 7,
    ]);

    $token = settingsAdminToken();

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->getJson('/api/v1/admin/settings/deployment')
        ->assertOk()
        ->assertJsonPath('data.items.0.key', 'currency.default_code')
        ->assertJsonPath('data.items.0.value', 'USD')
        ->assertJsonPath('data.items.0.env_var', 'LOTTERY_DEFAULT_CURRENCY')
        ->assertJsonFragment(['key' => 'draw.interval_minutes', 'value' => 7]);
});

test('admin settings batch update rejects empty items', function (): void {
    $token = settingsAdminToken();

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->putJson('/api/v1/admin/settings/batch', ['items' => []])
        ->assertUnprocessable();
});

test('admin can batch update settings with false and empty string values', function (): void {
    LotterySettings::put('settlement.auto_run_on_tick', true, 'settlement');
    LotterySettings::put('frontend.play_rules_html_zh', '<div>old</div>', 'frontend');

    $token = settingsAdminToken();

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->putJson('/api/v1/admin/settings/batch', [
            'items' => [
                ['key' => 'settlement.auto_run_on_tick', 'value' => false],
                ['key' => 'frontend.play_rules_html_zh', 'value' => ''],
            ],
        ])
        ->assertOk()
        ->assertJsonPath('data.items.0.key', 'frontend.play_rules_html_zh')
        ->assertJsonPath('data.items.0.value', '')
        ->assertJsonPath('data.items.1.key', 'settlement.auto_run_on_tick')
        ->assertJsonPath('data.items.1.value', false);

    expect(LotterySetting::query()->where('setting_key', 'settlement.auto_run_on_tick')->value('value_json'))->toBeFalse()
        ->and(LotterySetting::query()->where('setting_key', 'frontend.play_rules_html_zh')->value('value_json'))->toBe('');
});

test('admin can update single setting with false value', function (): void {
    LotterySettings::put('settlement.apply_rebate_to_payout', true, 'settlement');

    $token = settingsAdminToken();

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->putJson('/api/v1/admin/settings/settlement.apply_rebate_to_payout', [
            'value' => false,
        ])
        ->assertOk()
        ->assertJsonPath('data.key', 'settlement.apply_rebate_to_payout')
        ->assertJsonPath('data.value', false);

    expect(LotterySetting::query()->where('setting_key', 'settlement.apply_rebate_to_payout')->value('value_json'))->toBeFalse();
});

test('non payout manager cannot batch update settlement settings', function (): void {
    LotterySettings::put('settlement.auto_payout_on_tick', true, 'settlement');

    $token = settingsReadOnlyToken();

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->putJson('/api/v1/admin/settings/batch', [
            'items' => [
                ['key' => 'settlement.auto_payout_on_tick', 'value' => false],
            ],
        ])
        ->assertForbidden();

    expect(LotterySetting::query()->where('setting_key', 'settlement.auto_payout_on_tick')->value('value_json'))->toBeTrue();
});

test('non payout manager cannot update single settlement setting', function (): void {
    LotterySettings::put('settlement.auto_approve_on_tick', true, 'settlement');

    $token = settingsReadOnlyToken();

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->putJson('/api/v1/admin/settings/settlement.auto_approve_on_tick', [
            'value' => false,
        ])
        ->assertForbidden();

    expect(LotterySetting::query()->where('setting_key', 'settlement.auto_approve_on_tick')->value('value_json'))->toBeTrue();
});

test('wallet limits must be positive integer minor units', function (): void {
    LotterySettings::put('wallet.transfer_in_min_minor', 100, 'wallet');

    $token = settingsAdminToken();

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->putJson('/api/v1/admin/settings/batch', [
            'items' => [
                ['key' => 'wallet.transfer_in_min_minor', 'value' => 0],
            ],
        ])
        ->assertUnprocessable();

    expect(LotterySetting::query()->where('setting_key', 'wallet.transfer_in_min_minor')->value('value_json'))->toBe(100);
});

test('wallet max limit cannot be less than current min limit', function (): void {
    LotterySettings::put('wallet.transfer_in_min_minor', 100, 'wallet');
    LotterySettings::put('wallet.transfer_in_max_minor', 10_000, 'wallet');

    $token = settingsAdminToken();

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->putJson('/api/v1/admin/settings/wallet.transfer_in_max_minor', [
            'value' => 50,
        ])
        ->assertUnprocessable();

    expect(LotterySetting::query()->where('setting_key', 'wallet.transfer_in_max_minor')->value('value_json'))->toBe(10_000);
});

test('settings updates require permission for their setting group', function (): void {
    LotterySettings::put('draw.cooldown_minutes', 15, 'draw');
    LotterySettings::put('frontend.play_rules_html_zh', '<div>old</div>', 'frontend');

    $token = settingsReadOnlyToken();

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->putJson('/api/v1/admin/settings/draw.cooldown_minutes', [
            'value' => 10,
        ])
        ->assertForbidden();

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->putJson('/api/v1/admin/settings/frontend.play_rules_html_zh', [
            'value' => '<div>new</div>',
        ])
        ->assertOk();

    expect(LotterySetting::query()->where('setting_key', 'draw.cooldown_minutes')->value('value_json'))->toBe(15)
        ->and(LotterySetting::query()->where('setting_key', 'frontend.play_rules_html_zh')->value('value_json'))->toBe('<div>new</div>');
});
