<?php

use App\Models\AdminUser;
use App\Models\Player;
use App\Models\ReconcileJob;
use App\Models\TransferOrder;
use App\Support\SitePlatformRole;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Laravel\Sanctum\Sanctum;

uses(RefreshDatabase::class);

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

function reconcileScopeSuperToken(): string
{
    $admin = AdminUser::query()->create([
        'username' => 'reconcile_scope_super',
        'name' => 'Super',
        'email' => null,
        'password' => Hash::make('secret-strong'),
        'status' => 0,
    ]);
    grantSuperAdminRole($admin);

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

test('reconcile job list is scoped to site admin site', function (): void {
    $superToken = reconcileScopeSuperToken();

    $siteA = $this->withHeader('Authorization', 'Bearer '.$superToken)
        ->postJson('/api/v1/admin/integration-sites', [
            'code' => 'reconcile-a',
            'name' => 'Reconcile A',
            'admin_account' => [
                'username' => 'reconcile_a_admin',
                'nickname' => 'A Admin',
                'password' => 'secret-strong',
            ],
        ])
        ->assertCreated()
        ->json('data');

    $siteB = $this->withHeader('Authorization', 'Bearer '.$superToken)
        ->postJson('/api/v1/admin/integration-sites', [
            'code' => 'reconcile-b',
            'name' => 'Reconcile B',
            'admin_account' => [
                'username' => 'reconcile_b_admin',
                'nickname' => 'B Admin',
                'password' => 'secret-strong',
            ],
        ])
        ->assertCreated()
        ->json('data');

    $playerA = Player::query()->create([
        'site_code' => 'reconcile-a',
        'site_player_id' => 'scope-a-1',
        'username' => null,
        'nickname' => null,
        'default_currency' => 'NPR',
        'status' => 0,
    ]);
    $playerB = Player::query()->create([
        'site_code' => 'reconcile-b',
        'site_player_id' => 'scope-b-1',
        'username' => null,
        'nickname' => null,
        'default_currency' => 'NPR',
        'status' => 0,
    ]);

    foreach ([
        ['TO_scope_a', $playerA->id, (int) $siteA['id']],
        ['TO_scope_b', $playerB->id, (int) $siteB['id']],
    ] as [$transferNo, $playerId, $siteId]) {
        TransferOrder::query()->create([
            'transfer_no' => $transferNo,
            'player_id' => $playerId,
            'direction' => 'out',
            'currency_code' => 'NPR',
            'amount' => 100,
            'idempotent_key' => $transferNo.'-key',
            'status' => 'pending_reconcile',
            'external_request_payload' => null,
            'external_response_payload' => null,
            'external_ref_no' => null,
            'fail_reason' => 'main_site_timeout',
            'finished_at' => null,
            'created_at' => now()->subHours(2),
        ]);

        $this->withHeader('Authorization', 'Bearer '.$superToken)
            ->postJson('/api/v1/admin/reconcile-jobs', [
                'reconcile_type' => 'wallet_transfer',
                'date_from' => now()->subDay()->toDateString(),
                'date_to' => now()->toDateString(),
                'player_id' => $playerId,
            ])
            ->assertOk();
    }

    $jobA = ReconcileJob::query()->where('admin_site_id', (int) $siteA['id'])->latest('id')->first();
    $jobB = ReconcileJob::query()->where('admin_site_id', (int) $siteB['id'])->latest('id')->first();
    expect($jobA)->not->toBeNull()
        ->and($jobB)->not->toBeNull()
        ->and((int) $jobA->admin_site_id)->toBe((int) $siteA['id'])
        ->and((int) $jobB->admin_site_id)->toBe((int) $siteB['id']);

    $siteAdminA = AdminUser::query()->where('username', 'reconcile_a_admin')->firstOrFail();
    $siteAdminB = AdminUser::query()->where('username', 'reconcile_b_admin')->firstOrFail();
    expect(SitePlatformRole::userHasSiteAdminRole($siteAdminA))->toBeTrue()
        ->and($siteAdminA->isSuperAdmin())->toBeFalse()
        ->and($siteAdminA->accessibleAdminSiteIds())->toBe([(int) $siteA['id']])
        ->and($siteAdminA->primaryAgentNode())->toBeNull()
        ->and($siteAdminB->accessibleAdminSiteIds())->toBe([(int) $siteB['id']]);

    Sanctum::actingAs($siteAdminA, ['*']);
    $idsA = collect($this->getJson('/api/v1/admin/reconcile-jobs')
        ->assertOk()
        ->json('data.items'))
        ->pluck('id')
        ->all();

    Sanctum::actingAs($siteAdminB, ['*']);
    $idsB = collect($this->getJson('/api/v1/admin/reconcile-jobs')
        ->assertOk()
        ->json('data.items'))
        ->pluck('id')
        ->all();

    expect($idsA)->toEqual([(int) $jobA->id])
        ->and($idsB)->toEqual([(int) $jobB->id]);

    Sanctum::actingAs($siteAdminA, ['*']);
    $this->getJson('/api/v1/admin/reconcile-jobs/'.$jobB->id.'/items')
        ->assertForbidden();
});

test('site admin reconcile scan only includes own site transfer orders', function (): void {
    $superToken = reconcileScopeSuperToken();

    $this->withHeader('Authorization', 'Bearer '.$superToken)
        ->postJson('/api/v1/admin/integration-sites', [
            'code' => 'scan-scope-a',
            'name' => 'Scan Scope A',
            'admin_account' => [
                'username' => 'scan_scope_a_admin',
                'nickname' => 'Scan A',
                'password' => 'secret-strong',
            ],
        ])
        ->assertCreated();

    $defaultSiteCode = (string) DB::table('admin_sites')->where('is_default', true)->value('code');

    $ownPlayer = Player::query()->create([
        'site_code' => 'scan-scope-a',
        'site_player_id' => 'scan-own',
        'username' => null,
        'nickname' => null,
        'default_currency' => 'NPR',
        'status' => 0,
    ]);
    $otherPlayer = Player::query()->create([
        'site_code' => $defaultSiteCode,
        'site_player_id' => 'scan-other',
        'username' => null,
        'nickname' => null,
        'default_currency' => 'NPR',
        'status' => 0,
    ]);

    foreach ([['TO_scan_own', $ownPlayer->id], ['TO_scan_other', $otherPlayer->id]] as [$transferNo, $playerId]) {
        TransferOrder::query()->create([
            'transfer_no' => $transferNo,
            'player_id' => $playerId,
            'direction' => 'out',
            'currency_code' => 'NPR',
            'amount' => 200,
            'idempotent_key' => $transferNo.'-key',
            'status' => 'pending_reconcile',
            'external_request_payload' => null,
            'external_response_payload' => null,
            'external_ref_no' => null,
            'fail_reason' => 'main_site_timeout',
            'finished_at' => null,
            'created_at' => now()->subHours(2),
        ]);
    }

    $siteAdmin = AdminUser::query()->where('username', 'scan_scope_a_admin')->firstOrFail();
    Sanctum::actingAs($siteAdmin, ['*']);

    $this->postJson('/api/v1/admin/reconcile-jobs', [
        'reconcile_type' => 'wallet_transfer',
        'date_from' => now()->subDay()->toDateString(),
        'date_to' => now()->toDateString(),
    ])
        ->assertOk()
        ->assertJsonPath('data.item_count', 1);

    $job = ReconcileJob::query()->latest('id')->firstOrFail();
    expect($job->items()->value('side_a_ref'))->toBe('TO_scan_own');
});
