<?php

namespace Tests\Feature;

use App\Models\User;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\File;
use Tests\TestCase;

class Phase9FullAuthorizationAuditTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        $this->seed(DatabaseSeeder::class);
    }

    public function test_full_authorization_coverage_audit_passes_in_strict_mode(): void
    {
        $exitCode = Artisan::call('authorization:coverage-audit', [
            '--strict' => true,
        ]);

        $this->assertSame(0, $exitCode, Artisan::output());

        Artisan::call('authorization:coverage-audit', ['--json' => true]);
        $report = $this->decodeJsonReport(Artisan::output());

        $this->assertSame('pass', $report['status']);
        $this->assertSame(0, $report['summary']['errors']);
        $this->assertGreaterThan(0, $report['summary']['application_routes']);
        $this->assertGreaterThan(0, $report['summary']['permission_protected_routes']);
        $this->assertGreaterThan(0, $report['summary']['policy_files']);
        $this->assertSame($report['summary']['policy_files'], $report['summary']['registered_policies']);
        $this->assertGreaterThan(0, $report['summary']['application_commands']);
        $this->assertGreaterThan(0, $report['summary']['scheduled_commands']);
        $this->assertGreaterThan(0, $report['summary']['queued_jobs']);
    }

    public function test_direct_role_authorization_audit_passes_in_strict_mode(): void
    {
        $exitCode = Artisan::call('authorization:audit-role-checks', [
            '--strict' => true,
        ]);

        $this->assertSame(0, $exitCode, Artisan::output());

        Artisan::call('authorization:audit-role-checks', ['--json' => true]);
        $report = $this->decodeJsonReport(Artisan::output());
        $this->assertSame('pass', $report['status']);
        $this->assertSame(0, $report['summary']['unapproved_authorization_checks']);
    }

    public function test_scheduled_and_policy_execution_surfaces_have_no_gaps(): void
    {
        Artisan::call('authorization:coverage-audit', ['--json' => true]);
        $report = $this->decodeJsonReport(Artisan::output());

        $this->assertSame([], $report['policies']['missing']);
        $this->assertSame([], $report['policies']['errors']);
        $this->assertSame([], $report['execution_surfaces']['errors']);

        foreach ($report['execution_surfaces']['scheduled'] as $scheduledCommand) {
            $this->assertContains($scheduledCommand, $report['execution_surfaces']['commands']);
        }
    }

    public function test_phase_9_does_not_replace_existing_http_permission_inventory(): void
    {
        $path = storage_path('framework/testing/phase9-authorization-inventory.json');
        File::delete($path);

        try {
            $exitCode = Artisan::call('authorization:inventory', [
                '--strict' => true,
                '--output' => $path,
            ]);

            $this->assertSame(0, $exitCode, Artisan::output());
            $this->assertFileExists($path);

            $report = json_decode(File::get($path), true, 512, JSON_THROW_ON_ERROR);
            $this->assertSame('pass', $report['status']);
            $this->assertSame(0, $report['summary']['errors']);
        } finally {
            File::delete($path);
        }
    }

    public function test_legacy_has_permission_wrapper_has_been_removed(): void
    {
        $this->assertFalse(method_exists(User::class, 'hasPermission'));
    }

    /** @return array<string, mixed> */
    private function decodeJsonReport(string $output): array
    {
        $start = strpos($output, '{');
        $end = strrpos($output, '}');

        $this->assertNotFalse($start, 'Authorization command output did not contain a JSON object. Output: '.$output);
        $this->assertNotFalse($end, 'Authorization command output did not contain a complete JSON object. Output: '.$output);

        return json_decode(
            substr($output, $start, $end - $start + 1),
            true,
            512,
            JSON_THROW_ON_ERROR,
        );
    }
}
