<?php

use App\Models\AuditLog;
use App\Models\Policy;
use App\Models\ProviderConnection;
use App\Models\ProviderCredential;
use App\Models\ManagedEnvironment;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\GraphLogger;
use App\Services\Graph\GraphResponse;
use App\Services\Intune\PolicySyncService;
use App\Support\Audit\AuditActionId;
use Illuminate\Foundation\Testing\RefreshDatabase;

use function Pest\Laravel\mock;

uses(RefreshDatabase::class);

function tenantWithDefaultMicrosoftConnectionForProviderMissing(array $attributes = []): ManagedEnvironment
{
    $tenant = ManagedEnvironment::factory()->create($attributes + [
        'status' => 'active',
        'app_client_id' => null,
        'app_client_secret' => null,
    ]);

    $connection = ProviderConnection::factory()->consentGranted()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'provider' => 'microsoft',
        'is_default' => true,
        'consent_status' => 'granted',
        'entra_tenant_id' => (string) ($tenant->managed_environment_id ?: 'tenant-'.$tenant->getKey()),
    ]);

    ProviderCredential::factory()->create([
        'provider_connection_id' => (int) $connection->getKey(),
        'type' => 'client_secret',
        'payload' => [
            'client_id' => 'provider-client-'.$tenant->getKey(),
            'client_secret' => 'provider-secret-'.$tenant->getKey(),
        ],
    ]);

    return $tenant;
}

it('marks previously observed policies missing when provider list omits them', function (): void {
    $tenant = tenantWithDefaultMicrosoftConnectionForProviderMissing();

    $present = Policy::factory()->create([
        'managed_environment_id' => $tenant->id,
        'external_id' => 'policy-present',
        'policy_type' => 'deviceConfiguration',
        'display_name' => 'Old present',
        'ignored_at' => null,
        'missing_from_provider_at' => null,
    ]);

    $missing = Policy::factory()->create([
        'managed_environment_id' => $tenant->id,
        'external_id' => 'policy-missing',
        'policy_type' => 'deviceConfiguration',
        'display_name' => 'Missing from provider',
        'ignored_at' => null,
        'missing_from_provider_at' => null,
    ]);

    mock(GraphLogger::class)
        ->shouldReceive('logRequest', 'logResponse')
        ->zeroOrMoreTimes()
        ->andReturnNull();

    mock(GraphClientInterface::class)
        ->shouldReceive('listPolicies')
        ->once()
        ->with('deviceConfiguration', mockery::type('array'))
        ->andReturn(new GraphResponse(
            success: true,
            data: [
                [
                    'id' => 'policy-present',
                    'displayName' => 'Provider present',
                    'platform' => 'windows',
                ],
            ],
        ));

    app(PolicySyncService::class)->syncPolicies($tenant, [
        ['type' => 'deviceConfiguration', 'platform' => 'windows'],
    ]);

    $present->refresh();
    $missing->refresh();

    expect($present->display_name)->toBe('Provider present')
        ->and($present->ignored_at)->toBeNull()
        ->and($present->missing_from_provider_at)->toBeNull()
        ->and($missing->ignored_at)->toBeNull()
        ->and($missing->missing_from_provider_at)->not->toBeNull()
        ->and($missing->visibilityState())->toBe(Policy::VISIBILITY_PROVIDER_MISSING);

    expect(AuditLog::query()
        ->where('managed_environment_id', $tenant->id)
        ->where('action', AuditActionId::PolicyProviderMissingDetected->value)
        ->where('resource_id', (string) $missing->getKey())
        ->exists())->toBeTrue();
});

it('clears provider missing on reappearance without clearing local ignore', function (): void {
    $tenant = tenantWithDefaultMicrosoftConnectionForProviderMissing();

    $policy = Policy::factory()->create([
        'managed_environment_id' => $tenant->id,
        'external_id' => 'policy-returned',
        'policy_type' => 'deviceConfiguration',
        'display_name' => 'Returned policy',
        'ignored_at' => now()->subDay(),
        'missing_from_provider_at' => now()->subDay(),
    ]);

    mock(GraphLogger::class)
        ->shouldReceive('logRequest', 'logResponse')
        ->zeroOrMoreTimes()
        ->andReturnNull();

    mock(GraphClientInterface::class)
        ->shouldReceive('listPolicies')
        ->once()
        ->with('deviceConfiguration', mockery::type('array'))
        ->andReturn(new GraphResponse(
            success: true,
            data: [
                [
                    'id' => 'policy-returned',
                    'displayName' => 'Returned from provider',
                    'platform' => 'windows',
                ],
            ],
        ));

    app(PolicySyncService::class)->syncPolicies($tenant, [
        ['type' => 'deviceConfiguration', 'platform' => 'windows'],
    ]);

    $policy->refresh();

    expect($policy->display_name)->toBe('Returned from provider')
        ->and($policy->ignored_at)->not->toBeNull()
        ->and($policy->missing_from_provider_at)->toBeNull()
        ->and($policy->visibilityState())->toBe(Policy::VISIBILITY_IGNORED_LOCALLY);

    expect(AuditLog::query()
        ->where('managed_environment_id', $tenant->id)
        ->where('action', AuditActionId::PolicyProviderMissingCleared->value)
        ->where('resource_id', (string) $policy->getKey())
        ->exists())->toBeTrue();
});
