<?php

use App\Models\AdminRole;
use App\Models\AdminSite;
use App\Models\AdminUser;
use App\Models\ReportJob;
use Laravel\Sanctum\Sanctum;
use App\Services\AuditLogger;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

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

/**
 * @param  list<string>  $permissionCodes
 */
function makeReportScopeAdmin(string $username, int $siteId, array $permissionCodes): AdminUser
{
    $admin = AdminUser::query()->create([
        'username' => $username,
        'name' => $username,
        'email' => null,
        'password' => Hash::make('secret-strong'),
        'status' => 0,
    ]);

    $role = AdminRole::query()->create([
        'slug' => 'report_scope_'.$username,
        'code' => 'report_scope_'.$username,
        'name' => 'Report Scope '.$username,
        'scope_type' => AdminRole::SCOPE_SYSTEM,
        'status' => 1,
        'is_system' => false,
        'sort_order' => 0,
    ]);

    $actionIds = DB::table('admin_menu_actions')
        ->whereIn('permission_code', $permissionCodes)
        ->pluck('id');

    foreach ($actionIds as $actionId) {
        DB::table('admin_role_menu_actions')->insert([
            'role_id' => (int) $role->id,
            'menu_action_id' => (int) $actionId,
        ]);
    }

    DB::table('admin_user_site_roles')->insert([
        'admin_user_id' => (int) $admin->id,
        'site_id' => $siteId,
        'role_id' => (int) $role->id,
        'granted_at' => now(),
    ]);

    return $admin;
}

function makeReportScopeJob(AdminUser $owner, string $jobNo, string $reportType = 'daily_profit_summary'): ReportJob
{
    return ReportJob::query()->create([
        'job_no' => $jobNo,
        'admin_user_id' => (int) $owner->id,
        'report_type' => $reportType,
        'export_format' => 'csv',
        'filter_json' => [
            'date_from' => now()->toDateString(),
            'date_to' => now()->toDateString(),
        ],
        'status' => 'completed',
        'output_path' => 'reports/'.$jobNo.'.csv',
        'finished_at' => now(),
    ]);
}

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

    return $admin;
}

test('report job list show and download are owner only with an explicit super admin exception', function (): void {
    $siteAId = (int) DB::table('admin_sites')->where('is_default', true)->value('id');
    $siteB = AdminSite::query()->create([
        'code' => 'report-scope-b',
        'name' => 'Report Scope B',
        'currency_code' => 'NPR',
        'status' => 1,
        'is_default' => false,
    ]);
    $permissions = ['service.report.view', 'service.report.export'];
    $owner = makeReportScopeAdmin('report_owner', $siteAId, $permissions);
    $sameSitePeer = makeReportScopeAdmin('report_same_site_peer', $siteAId, $permissions);
    $crossSitePeer = makeReportScopeAdmin('report_cross_site_peer', (int) $siteB->id, $permissions);

    $ownedJob = makeReportScopeJob($owner, 'RPT-SCOPE-OWN');
    $sameSiteJob = makeReportScopeJob($sameSitePeer, 'RPT-SCOPE-SAME');
    $crossSiteJob = makeReportScopeJob($crossSitePeer, 'RPT-SCOPE-CROSS');

    $deletedCreator = makeReportScopeAdmin('report_deleted_creator', $siteAId, $permissions);
    $orphanedJob = makeReportScopeJob($deletedCreator, 'RPT-SCOPE-ORPHAN');
    $deletedCreator->delete();
    $orphanedJob->refresh();
    expect($orphanedJob->admin_user_id)->toBeNull();

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

    expect($visibleIds)->toBe([(int) $ownedJob->id]);

    $this->getJson('/api/v1/admin/report-jobs/'.$ownedJob->id)->assertOk();
    $this->get('/api/v1/admin/report-jobs/'.$ownedJob->id.'/download')->assertOk();

    foreach ([$sameSiteJob, $crossSiteJob, $orphanedJob] as $deniedJob) {
        $this->getJson('/api/v1/admin/report-jobs/'.$deniedJob->id)->assertForbidden();
        $this->get('/api/v1/admin/report-jobs/'.$deniedJob->id.'/download')->assertForbidden();
    }

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

    expect($superVisibleIds)->toContain(
        (int) $ownedJob->id,
        (int) $sameSiteJob->id,
        (int) $crossSiteJob->id,
        (int) $orphanedJob->id,
    );

    $this->getJson('/api/v1/admin/report-jobs/'.$orphanedJob->id)->assertOk();
    $this->get('/api/v1/admin/report-jobs/'.$orphanedJob->id.'/download')->assertOk();
});

test('sensitive report types require their own capabilities and risk export is super admin only', function (): void {
    $siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id');
    $base = makeReportScopeAdmin('report_base_exporter', $siteId, [
        'service.report.view',
        'service.report.export',
    ]);
    $auditor = makeReportScopeAdmin('report_auditor', $siteId, [
        'service.report.view',
        'service.report.export',
        'service.audit.view',
    ]);
    $riskViewer = makeReportScopeAdmin('report_risk_viewer', $siteId, [
        'service.report.view',
        'service.report.export',
        'risk.monitor.view',
    ]);

    Sanctum::actingAs($base, ['*']);
    $this->postJson('/api/v1/admin/report-jobs', [
        'report_type' => 'audit_operation_report',
    ])->assertForbidden();
    $legacyAuditJob = makeReportScopeJob($base, 'RPT-SENSITIVE-AUDIT', 'audit_operation_report');
    $this->get('/api/v1/admin/report-jobs/'.$legacyAuditJob->id.'/download')->assertForbidden();

    Sanctum::actingAs($auditor, ['*']);
    $this->postJson('/api/v1/admin/report-jobs', [
        'report_type' => 'audit_operation_report',
    ])->assertOk();

    Sanctum::actingAs($riskViewer, ['*']);
    $legacyRiskJob = makeReportScopeJob($riskViewer, 'RPT-SENSITIVE-RISK', 'hot_number_risk_report');
    foreach (['hot_number_risk_report', 'sold_out_number_report'] as $reportType) {
        $this->postJson('/api/v1/admin/report-jobs', [
            'report_type' => $reportType,
        ])->assertForbidden();
    }
    $this->get('/api/v1/admin/report-jobs/'.$legacyRiskJob->id.'/download')->assertForbidden();

    $super = makeReportScopeSuperAdmin();
    Sanctum::actingAs($super, ['*']);
    foreach (['hot_number_risk_report', 'sold_out_number_report'] as $reportType) {
        $created = $this->postJson('/api/v1/admin/report-jobs', [
            'report_type' => $reportType,
        ])->assertOk();
        $this->get('/api/v1/admin/report-jobs/'.(int) $created->json('data.id').'/download')->assertOk();
    }
});

test('non super audit export is limited to the current actor', function (): void {
    $siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id');
    $auditor = makeReportScopeAdmin('report_audit_owner', $siteId, [
        'service.report.view',
        'service.report.export',
        'service.audit.view',
    ]);
    $other = makeReportScopeAdmin('report_audit_other', $siteId, [
        'service.report.view',
        'service.report.export',
        'service.audit.view',
    ]);

    AuditLogger::recordForAdmin($auditor, null, 'audit_scope_own', 'own_action', null, null, null, null);
    AuditLogger::recordForAdmin($other, null, 'audit_scope_other', 'other_action', null, null, null, null);

    Sanctum::actingAs($auditor, ['*']);
    $create = $this->postJson('/api/v1/admin/report-jobs', [
        'report_type' => 'audit_operation_report',
        'export_format' => 'csv',
        'parameters' => [
            'date_from' => now()->toDateString(),
            'date_to' => now()->toDateString(),
        ],
    ])->assertOk();

    $content = $this->get('/api/v1/admin/report-jobs/'.(int) $create->json('data.id').'/download')
        ->assertOk()
        ->streamedContent();

    expect($content)->toContain('audit_scope_own')
        ->not->toContain('audit_scope_other');
});
