<?php

declare(strict_types=1);

use App\Jobs\GenerateReviewPackJob;
use App\Models\OperationRun;
use App\Models\ProductUsageEvent;
use App\Models\StoredReport;
use App\Models\ManagedEnvironment;
use App\Services\EntraAdminRoles\EntraAdminRolesReportService;
use App\Services\EntraAdminRoles\HighPrivilegeRoleCatalog;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\GraphResponse;
use App\Services\PermissionPosture\PermissionPostureFindingGenerator;
use App\Services\Providers\MicrosoftGraphOptionsResolver;
use App\Services\ReviewPackService;
use App\Support\OperationRunType;
use App\Support\ProductTelemetry\ProductUsageEventCatalog;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Storage;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    Storage::fake('exports');
});

function productTelemetryRoleDefinitions(): array
{
    return [[
        'id' => 'def-ga-001',
        'displayName' => 'Global Administrator',
        'templateId' => '62e90394-69f5-4237-9190-012177145e10',
        'isBuiltIn' => true,
    ]];
}

function productTelemetryRoleAssignments(): array
{
    return [[
        'id' => 'assign-1',
        'roleDefinitionId' => 'def-ga-001',
        'principalId' => 'user-aaa',
        'directoryScopeId' => '/',
        'principal' => [
            '@odata.type' => '#microsoft.graph.user',
            'displayName' => 'Alice Admin',
        ],
    ]];
}

function buildTelemetryGraphMock(): GraphClientInterface
{
    return new class implements GraphClientInterface
    {
        public function listPolicies(string $policyType, array $options = []): GraphResponse
        {
            return match ($policyType) {
                'entraRoleDefinitions' => new GraphResponse(success: true, data: productTelemetryRoleDefinitions(), status: 200),
                'entraRoleAssignments' => new GraphResponse(success: true, data: productTelemetryRoleAssignments(), status: 200),
                default => new GraphResponse(success: false, status: 404, errors: ['Unknown type']),
            };
        }

        public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse
        {
            return new GraphResponse(success: false, status: 501);
        }

        public function getOrganization(array $options = []): GraphResponse
        {
            return new GraphResponse(success: false, status: 501);
        }

        public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse
        {
            return new GraphResponse(success: false, status: 501);
        }

        public function getServicePrincipalPermissions(array $options = []): GraphResponse
        {
            return new GraphResponse(success: false, status: 501);
        }

        public function request(string $method, string $path, array $options = []): GraphResponse
        {
            return new GraphResponse(success: false, status: 501);
        }
    };
}

function buildTelemetryReportService(): EntraAdminRolesReportService
{
    return new EntraAdminRolesReportService(
        graphClient: buildTelemetryGraphMock(),
        catalog: new HighPrivilegeRoleCatalog,
        graphOptionsResolver: app(MicrosoftGraphOptionsResolver::class),
        productTelemetryRecorder: app(\App\Support\ProductTelemetry\ProductTelemetryRecorder::class),
    );
}

it('records telemetry when a user-initiated Entra admin roles report is created', function (): void {
    $tenant = ManagedEnvironment::factory()->create();
    [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'operator');
    ensureDefaultProviderConnection($tenant);

    $run = OperationRun::factory()->forTenant($tenant)->create([
        'user_id' => (int) $user->getKey(),
        'initiator_name' => $user->name,
        'type' => OperationRunType::InventorySync->value,
    ]);

    $result = buildTelemetryReportService()->generate($tenant, $run);

    $event = ProductUsageEvent::query()->sole();
    $serializedEvent = json_encode($event->toArray(), JSON_THROW_ON_ERROR);

    expect($result->created)->toBeTrue()
        ->and($event->event_name)->toBe(ProductUsageEventCatalog::STORED_REPORT_CREATED)
        ->and($event->subject_type)->toBe('stored_report')
        ->and($event->subject_id)->toBe((string) $result->storedReportId)
        ->and($event->metadata)->toBe([
            'report_type' => StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES,
        ])
        ->and($serializedEvent)->not->toContain('@')
        ->and($serializedEvent)->not->toContain('Alice Admin')
        ->and($serializedEvent)->not->toContain((string) $tenant->name);
});

it('records telemetry when a user-initiated permission posture report is created', function (): void {
    $tenant = ManagedEnvironment::factory()->create();
    [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'operator');

    $run = OperationRun::factory()->forTenant($tenant)->create([
        'user_id' => (int) $user->getKey(),
        'initiator_name' => $user->name,
        'type' => OperationRunType::InventorySync->value,
    ]);

    $result = app(PermissionPostureFindingGenerator::class)->generate($tenant, [
        'overall_status' => 'missing',
        'permissions' => [
            [
                'key' => 'DeviceManagementApps.ReadWrite.All',
                'type' => 'application',
                'status' => 'missing',
                'features' => ['policy-sync'],
            ],
        ],
        'last_refreshed_at' => now()->toIso8601String(),
    ], $run);

    $event = ProductUsageEvent::query()->sole();
    $serializedEvent = json_encode($event->toArray(), JSON_THROW_ON_ERROR);

    expect($event->event_name)->toBe(ProductUsageEventCatalog::STORED_REPORT_CREATED)
        ->and($event->subject_type)->toBe('stored_report')
        ->and($event->subject_id)->toBe((string) $result->storedReportId)
        ->and($event->metadata)->toBe([
            'report_type' => StoredReport::REPORT_TYPE_PERMISSION_POSTURE,
        ])
        ->and($serializedEvent)->not->toContain('@')
        ->and($serializedEvent)->not->toContain('DeviceManagementApps.ReadWrite.All')
        ->and($serializedEvent)->not->toContain((string) $tenant->name);
});

it('records telemetry when a user requests a review pack generation', function (): void {
    $tenant = ManagedEnvironment::factory()->create();
    [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'operator');

    seedEnvironmentReviewEvidence($tenant);
    Notification::fake();

    $pack = app(ReviewPackService::class)->generate($tenant, $user, [
        'include_pii' => true,
        'include_operations' => true,
    ]);

    $event = ProductUsageEvent::query()->where('event_name', ProductUsageEventCatalog::REVIEW_PACK_REQUESTED)->sole();
    $serializedEvent = json_encode($event->toArray(), JSON_THROW_ON_ERROR);

    expect($pack->status)->toBe('queued')
        ->and($event->subject_type)->toBe('review_pack')
        ->and($event->subject_id)->toBe((string) $pack->getKey())
        ->and($event->metadata)->toBe([
            'source_surface' => 'tenant',
            'include_operations' => true,
            'include_pii' => true,
        ])
        ->and($serializedEvent)->not->toContain('@')
        ->and($serializedEvent)->not->toContain((string) $tenant->name);

    $job = new GenerateReviewPackJob(
        reviewPackId: (int) $pack->getKey(),
        operationRunId: (int) $pack->operation_run_id,
    );

    app()->call([$job, 'handle']);
});