<?php

namespace MintyPHP\Tests\Module\Audit;

use MintyPHP\Module\Audit\AuditAuthorizationPolicy;
use MintyPHP\Service\Access\PermissionService;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

final class AuditAuthorizationPolicyTest extends TestCase
{
    private PermissionService&MockObject $permissionService;
    private AuditAuthorizationPolicy $policy;

    protected function setUp(): void
    {
        $this->permissionService = $this->createMock(PermissionService::class);
        $this->policy = new AuditAuthorizationPolicy($this->permissionService);
    }

    public static function abilityPermissionProvider(): array
    {
        return [
            [AuditAuthorizationPolicy::ABILITY_API_AUDIT_VIEW, 'audit.api.view'],
            [AuditAuthorizationPolicy::ABILITY_API_AUDIT_PURGE, 'audit.api.purge'],
            [AuditAuthorizationPolicy::ABILITY_SYSTEM_AUDIT_VIEW, 'audit.system.view'],
            [AuditAuthorizationPolicy::ABILITY_SYSTEM_AUDIT_PURGE, 'audit.system.purge'],
            [AuditAuthorizationPolicy::ABILITY_IMPORTS_AUDIT_VIEW, 'audit.imports.view'],
            [AuditAuthorizationPolicy::ABILITY_IMPORTS_AUDIT_PURGE, 'audit.imports.purge'],
            [AuditAuthorizationPolicy::ABILITY_USER_LIFECYCLE_VIEW, 'audit.user_lifecycle.view'],
            [AuditAuthorizationPolicy::ABILITY_USER_LIFECYCLE_RESTORE, 'audit.user_lifecycle.restore'],
        ];
    }

    #[DataProvider('abilityPermissionProvider')]
    public function testSupportsAllAuditAbilities(string $ability, string $permission): void
    {
        self::assertTrue($this->policy->supports($ability));
        self::assertNotSame('', $permission);
    }

    public function testSupportsRejectsUnknownAbility(): void
    {
        self::assertFalse($this->policy->supports('audit.unknown.ability'));
    }

    #[DataProvider('abilityPermissionProvider')]
    public function testAuthorizeUsesMappedPermissionKey(string $ability, string $permission): void
    {
        $this->permissionService->expects($this->once())
            ->method('userHas')
            ->with(17, $permission)
            ->willReturn(true);

        $decision = $this->policy->authorize($ability, ['actor_user_id' => 17]);

        self::assertTrue($decision->isAllowed());
    }

    public function testAuthorizeDeniesMissingActorIdWithoutPermissionLookup(): void
    {
        $this->permissionService->expects($this->never())->method('userHas');

        $decision = $this->policy->authorize(AuditAuthorizationPolicy::ABILITY_API_AUDIT_VIEW, []);

        self::assertFalse($decision->isAllowed());
        self::assertSame(403, $decision->status());
    }

    public function testAuthorizeDeniesUnknownAbilityWithoutPermissionLookup(): void
    {
        $this->permissionService->expects($this->never())->method('userHas');

        $decision = $this->policy->authorize('audit.unknown.ability', ['actor_user_id' => 17]);

        self::assertFalse($decision->isAllowed());
        self::assertSame(403, $decision->status());
    }

    public function testPolicyPermissionKeysMatchManifestPermissionKeys(): void
    {
        $manifest = require dirname(__DIR__, 3) . '/module.php';
        $manifestPermissions = is_array($manifest['permissions'] ?? null) ? $manifest['permissions'] : [];

        $manifestKeys = [];
        foreach ($manifestPermissions as $permission) {
            if (is_array($permission) && isset($permission['key'])) {
                $manifestKeys[] = (string) $permission['key'];
            }
        }
        sort($manifestKeys);

        $policyKeys = [];
        foreach (self::abilityPermissionProvider() as $pair) {
            $policyKeys[] = $pair[1];
        }
        $policyKeys = array_values(array_unique($policyKeys));
        sort($policyKeys);

        self::assertSame($manifestKeys, $policyKeys);
    }
}
