<?php

namespace MintyPHP\Tests\Service\Access;

use MintyPHP\Service\Access\PermissionService;
use MintyPHP\Service\Access\TenantAuthorizationPolicy;
use MintyPHP\Service\Tenant\TenantScopeService;
use PHPUnit\Framework\TestCase;

class TenantAuthorizationPolicyTest extends TestCase
{
    use AuthorizationPolicyTestSupport;

    public function testViewRequiresTenantsViewPermission(): void
    {
        $permissionService = $this->createMock(PermissionService::class);
        $permissionService->expects($this->once())
            ->method('userHas')
            ->with(11, PermissionService::TENANTS_VIEW)
            ->willReturn(false);

        $scopeGateway = $this->createMock(TenantScopeService::class);
        $scopeGateway->expects($this->never())->method('getUserTenantIds');
        $scopeGateway->expects($this->never())->method('isStrict');
        $scopeGateway->expects($this->never())->method('hasGlobalAccess');

        $policy = new TenantAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(TenantAuthorizationPolicy::ABILITY_ADMIN_TENANTS_VIEW, [
            'actor_user_id' => 11,
        ]);

        $this->assertForbiddenDecision($decision);
    }

    public function testViewReturnsCapabilitiesAndScopeData(): void
    {
        $permissionService = $this->permissionGatewayAllowing([
            11 => [PermissionService::TENANTS_VIEW, PermissionService::TENANTS_CREATE],
        ]);

        $scopeGateway = $this->createMock(TenantScopeService::class);
        $scopeGateway->expects($this->once())
            ->method('getUserTenantIds')
            ->with(11)
            ->willReturn([2, 3]);
        $scopeGateway->expects($this->once())
            ->method('isStrict')
            ->willReturn(true);
        $scopeGateway->expects($this->once())
            ->method('hasGlobalAccess')
            ->with(11)
            ->willReturn(false);

        $policy = new TenantAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(TenantAuthorizationPolicy::ABILITY_ADMIN_TENANTS_VIEW, [
            'actor_user_id' => 11,
        ]);

        $capabilities = $decision->attribute('capabilities', []);
        $this->assertTrue($decision->isAllowed());
        $this->assertTrue((bool) ($capabilities['can_create_tenant'] ?? false));
        $this->assertSame([2, 3], $capabilities['allowed_tenant_ids'] ?? []);
        $this->assertTrue((bool) ($capabilities['is_strict_scope'] ?? false));
        $this->assertFalse((bool) ($capabilities['has_global_access'] ?? true));
    }

    public function testCreateRequiresTenantsCreatePermission(): void
    {
        $permissionService = $this->createMock(PermissionService::class);
        $permissionService->expects($this->once())
            ->method('userHas')
            ->with(11, PermissionService::TENANTS_CREATE)
            ->willReturn(false);

        $scopeGateway = $this->createMock(TenantScopeService::class);
        $policy = new TenantAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(TenantAuthorizationPolicy::ABILITY_ADMIN_TENANTS_CREATE, [
            'actor_user_id' => 11,
        ]);

        $this->assertDeniedDecision($decision, 403, 'forbidden');
    }

    public function testCreateReturnsSsoAndCustomFieldCapabilities(): void
    {
        $permissionService = $this->permissionGatewayAllowing([
            11 => [PermissionService::TENANTS_CREATE, PermissionService::TENANTS_SSO_MANAGE],
        ]);

        $scopeGateway = $this->createMock(TenantScopeService::class);
        $policy = new TenantAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(TenantAuthorizationPolicy::ABILITY_ADMIN_TENANTS_CREATE, [
            'actor_user_id' => 11,
        ]);

        $capabilities = $decision->attribute('capabilities', []);
        $this->assertTrue($decision->isAllowed());
        $this->assertTrue((bool) ($capabilities['can_manage_sso'] ?? false));
        $this->assertFalse((bool) ($capabilities['can_manage_custom_fields'] ?? true));
    }

    public function testEditContextAllowsInScopeViewer(): void
    {
        $permissionService = $this->permissionGatewayAllowing([
            11 => [PermissionService::TENANTS_VIEW],
        ]);

        $scopeGateway = $this->createMock(TenantScopeService::class);
        $scopeGateway->expects($this->once())
            ->method('canAccess')
            ->with('tenants', 22, 11)
            ->willReturn(true);

        $policy = new TenantAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(TenantAuthorizationPolicy::ABILITY_ADMIN_TENANTS_EDIT_CONTEXT, [
            'actor_user_id' => 11,
            'target_tenant_id' => 22,
        ]);

        $this->assertTrue($decision->isAllowed());
        $capabilities = $decision->attribute('capabilities', []);
        $this->assertTrue((bool) ($capabilities['can_view_page'] ?? false));
        $this->assertFalse((bool) ($capabilities['can_update_tenant'] ?? true));
    }

    public function testEditContextDeniesOutOfScopeTenant(): void
    {
        $permissionService = $this->createMock(PermissionService::class);
        $scopeGateway = $this->createMock(TenantScopeService::class);
        $scopeGateway->expects($this->once())
            ->method('canAccess')
            ->with('tenants', 22, 11)
            ->willReturn(false);

        $policy = new TenantAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(TenantAuthorizationPolicy::ABILITY_ADMIN_TENANTS_EDIT_CONTEXT, [
            'actor_user_id' => 11,
            'target_tenant_id' => 22,
        ]);

        $this->assertDeniedDecision($decision, 403, 'permission_denied');
    }

    public function testEditSubmitRequiresTenantsUpdatePermission(): void
    {
        $permissionService = $this->permissionGatewayAllowing([
            11 => [PermissionService::TENANTS_VIEW],
        ]);

        $scopeGateway = $this->createMock(TenantScopeService::class);
        $scopeGateway->expects($this->once())
            ->method('canAccess')
            ->with('tenants', 22, 11)
            ->willReturn(true);

        $policy = new TenantAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(TenantAuthorizationPolicy::ABILITY_ADMIN_TENANTS_EDIT_SUBMIT, [
            'actor_user_id' => 11,
            'target_tenant_id' => 22,
            'input' => ['description' => 'Tenant A'],
        ]);

        $this->assertForbiddenDecision($decision);
    }

    public function testEditSubmitDeniesSsoFieldsWithoutSsoPermission(): void
    {
        $permissionService = $this->permissionGatewayAllowing([
            11 => [PermissionService::TENANTS_VIEW, PermissionService::TENANTS_UPDATE],
        ]);

        $scopeGateway = $this->createMock(TenantScopeService::class);
        $scopeGateway->expects($this->once())
            ->method('canAccess')
            ->with('tenants', 22, 11)
            ->willReturn(true);

        $policy = new TenantAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(TenantAuthorizationPolicy::ABILITY_ADMIN_TENANTS_EDIT_SUBMIT, [
            'actor_user_id' => 11,
            'target_tenant_id' => 22,
            'input' => ['microsoft_enabled' => '1'],
        ]);

        $this->assertForbiddenDecision($decision);
    }

    public function testEditSubmitDeniesAutoRememberModeWithoutSsoPermission(): void
    {
        $permissionService = $this->permissionGatewayAllowing([
            11 => [PermissionService::TENANTS_VIEW, PermissionService::TENANTS_UPDATE],
        ]);

        $scopeGateway = $this->createMock(TenantScopeService::class);
        $scopeGateway->expects($this->once())
            ->method('canAccess')
            ->with('tenants', 22, 11)
            ->willReturn(true);

        $policy = new TenantAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(TenantAuthorizationPolicy::ABILITY_ADMIN_TENANTS_EDIT_SUBMIT, [
            'actor_user_id' => 11,
            'target_tenant_id' => 22,
            'input' => ['microsoft_auto_remember_mode' => '1'],
        ]);

        $this->assertForbiddenDecision($decision);
    }

    public function testCustomFieldsManageRequiresPermissionAndScope(): void
    {
        $permissionService = $this->permissionGatewayAllowing([
            9 => [PermissionService::CUSTOM_FIELDS_MANAGE],
        ]);

        $scopeGateway = $this->createMock(TenantScopeService::class);
        $scopeGateway->expects($this->once())
            ->method('canAccess')
            ->with('tenants', 5, 9)
            ->willReturn(true);

        $policy = new TenantAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(TenantAuthorizationPolicy::ABILITY_ADMIN_TENANTS_CUSTOM_FIELDS_MANAGE, [
            'actor_user_id' => 9,
            'target_tenant_id' => 5,
        ]);

        $this->assertTrue($decision->isAllowed());
    }

    public function testCustomFieldsManageDeniedWhenOutOfScope(): void
    {
        $permissionService = $this->permissionGatewayAllowing([
            9 => [PermissionService::CUSTOM_FIELDS_MANAGE],
        ]);

        $scopeGateway = $this->createMock(TenantScopeService::class);
        $scopeGateway->expects($this->once())
            ->method('canAccess')
            ->with('tenants', 5, 9)
            ->willReturn(false);

        $policy = new TenantAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(TenantAuthorizationPolicy::ABILITY_ADMIN_TENANTS_CUSTOM_FIELDS_MANAGE, [
            'actor_user_id' => 9,
            'target_tenant_id' => 5,
        ]);

        $this->assertDeniedDecision($decision, 403, 'permission_denied');
    }

    public function testLogoViewAllowsTenantViewerInScope(): void
    {
        $permissionService = $this->permissionGatewayAllowing([
            15 => [PermissionService::TENANTS_VIEW],
        ]);

        $scopeGateway = $this->createMock(TenantScopeService::class);
        $scopeGateway->expects($this->once())
            ->method('canAccess')
            ->with('tenants', 9, 15)
            ->willReturn(true);

        $policy = new TenantAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(TenantAuthorizationPolicy::ABILITY_ADMIN_TENANTS_LOGO_VIEW, [
            'actor_user_id' => 15,
            'target_tenant_id' => 9,
        ]);

        $this->assertTrue($decision->isAllowed());
    }

    public function testMediaUpdateRequiresTenantsUpdatePermission(): void
    {
        $permissionService = $this->createMock(PermissionService::class);
        $scopeGateway = $this->createMock(TenantScopeService::class);
        $scopeGateway->expects($this->never())->method('canAccess');

        $policy = new TenantAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(TenantAuthorizationPolicy::ABILITY_ADMIN_TENANTS_MEDIA_UPDATE, [
            'actor_user_id' => 15,
            'target_tenant_id' => 9,
        ]);

        $this->assertDeniedDecision($decision, 403, 'forbidden');
    }

    public function testDeleteRequiresPermissionAndScope(): void
    {
        $permissionService = $this->permissionGatewayAllowing([
            5 => [PermissionService::TENANTS_DELETE],
        ]);

        $scopeGateway = $this->createMock(TenantScopeService::class);
        $scopeGateway->expects($this->once())
            ->method('canAccess')
            ->with('tenants', 17, 5)
            ->willReturn(true);

        $policy = new TenantAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(TenantAuthorizationPolicy::ABILITY_ADMIN_TENANTS_DELETE, [
            'actor_user_id' => 5,
            'target_tenant_id' => 17,
        ]);

        $this->assertTrue($decision->isAllowed());
    }
}
