<?php

namespace MintyPHP\Tests\Service\Access;

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

class DepartmentAuthorizationPolicyTest extends TestCase
{
    use AuthorizationPolicyTestSupport;

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

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

        $policy = new DepartmentAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_VIEW, [
            'actor_user_id' => 5,
        ]);

        $this->assertForbiddenDecision($decision);
    }

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

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

        $policy = new DepartmentAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_VIEW, [
            'actor_user_id' => 5,
        ]);

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

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

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

        $policy = new DepartmentAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_CREATE, [
            'actor_user_id' => 7,
        ]);

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

    public function testCreateDeniedInStrictScopeWithoutTenantAssignments(): void
    {
        $permissionService = $this->permissionGatewayAllowing([
            7 => [PermissionService::DEPARTMENTS_CREATE],
        ]);

        $scopeGateway = $this->createMock(TenantScopeService::class);
        $scopeGateway->expects($this->once())
            ->method('getUserTenantIds')
            ->with(7)
            ->willReturn([]);
        $scopeGateway->expects($this->once())
            ->method('isStrict')
            ->willReturn(true);

        $policy = new DepartmentAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_CREATE, [
            'actor_user_id' => 7,
        ]);

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

    public function testCreateReturnsScopeCapabilitiesWhenAllowed(): void
    {
        $permissionService = $this->permissionGatewayAllowing([
            7 => [PermissionService::DEPARTMENTS_CREATE],
        ]);

        $scopeGateway = $this->createMock(TenantScopeService::class);
        $scopeGateway->expects($this->once())
            ->method('getUserTenantIds')
            ->with(7)
            ->willReturn([4]);
        $scopeGateway->expects($this->once())
            ->method('isStrict')
            ->willReturn(true);

        $policy = new DepartmentAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_CREATE, [
            'actor_user_id' => 7,
        ]);

        $capabilities = $decision->attribute('capabilities', []);
        $this->assertTrue($decision->isAllowed());
        $this->assertSame([4], $capabilities['allowed_tenant_ids'] ?? []);
        $this->assertTrue((bool) ($capabilities['is_strict_scope'] ?? false));
    }

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

        $policy = new DepartmentAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_EDIT_CONTEXT, [
            'actor_user_id' => 5,
            'target_department_id' => 17,
        ]);

        $this->assertForbiddenDecision($decision);
    }

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

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

        $policy = new DepartmentAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_EDIT_CONTEXT, [
            'actor_user_id' => 5,
            'target_department_id' => 17,
        ]);

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

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

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

        $policy = new DepartmentAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_EDIT_CONTEXT, [
            'actor_user_id' => 5,
            'target_department_id' => 17,
        ]);

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

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

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

        $policy = new DepartmentAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_EDIT_CONTEXT, [
            'actor_user_id' => 5,
            'target_department_id' => 17,
        ]);

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

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

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

        $policy = new DepartmentAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_EDIT_SUBMIT, [
            'actor_user_id' => 5,
            'target_department_id' => 17,
        ]);

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

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

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

        $policy = new DepartmentAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_DELETE, [
            'actor_user_id' => 9,
            'target_department_id' => 44,
        ]);

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

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

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

        $policy = new DepartmentAuthorizationPolicy($permissionService, $scopeGateway);
        $decision = $policy->authorize(DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_DELETE, [
            'actor_user_id' => 9,
            'target_department_id' => 44,
        ]);

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