<?php

namespace MintyPHP\Service\Access;

use MintyPHP\App\Module\ModuleRegistry;
use MintyPHP\Service\Tenant\TenantScopeService;

class AccessPolicyFactory
{
    private ?UserAuthorizationPolicy $userAuthorizationPolicy = null;
    private ?RoleAuthorizationPolicy $roleAuthorizationPolicy = null;
    private ?PermissionAuthorizationPolicy $permissionAuthorizationPolicy = null;
    private ?OperationsAuthorizationPolicy $operationsAuthorizationPolicy = null;
    private ?SettingsAuthorizationPolicy $settingsAuthorizationPolicy = null;
    private ?TenantAuthorizationPolicy $tenantAuthorizationPolicy = null;
    private ?DepartmentAuthorizationPolicy $departmentAuthorizationPolicy = null;
    private ?AuthorizationService $authorizationService = null;
    private \Closure $moduleClassResolver;

    public function __construct(
        private readonly PermissionService $permissionService,
        private readonly TenantScopeService $tenantScopeService,
        private readonly ?ModuleRegistry $moduleRegistry = null,
        ?callable $moduleClassResolver = null
    ) {
        $this->moduleClassResolver = $moduleClassResolver !== null
            ? \Closure::fromCallable($moduleClassResolver)
            : static fn (string $class): ?object => null;
    }

    public function createUserAuthorizationPolicy(): UserAuthorizationPolicy
    {
        return $this->userAuthorizationPolicy ??= new UserAuthorizationPolicy(
            $this->permissionService,
            $this->tenantScopeService
        );
    }

    public function createRoleAuthorizationPolicy(): RoleAuthorizationPolicy
    {
        return $this->roleAuthorizationPolicy ??= new RoleAuthorizationPolicy($this->permissionService);
    }

    public function createPermissionAuthorizationPolicy(): PermissionAuthorizationPolicy
    {
        return $this->permissionAuthorizationPolicy ??= new PermissionAuthorizationPolicy($this->permissionService);
    }

    public function createOperationsAuthorizationPolicy(): OperationsAuthorizationPolicy
    {
        return $this->operationsAuthorizationPolicy ??= new OperationsAuthorizationPolicy($this->permissionService);
    }

    public function createSettingsAuthorizationPolicy(): SettingsAuthorizationPolicy
    {
        return $this->settingsAuthorizationPolicy ??= new SettingsAuthorizationPolicy($this->permissionService);
    }

    public function createTenantAuthorizationPolicy(): TenantAuthorizationPolicy
    {
        return $this->tenantAuthorizationPolicy ??= new TenantAuthorizationPolicy(
            $this->permissionService,
            $this->tenantScopeService
        );
    }

    public function createDepartmentAuthorizationPolicy(): DepartmentAuthorizationPolicy
    {
        return $this->departmentAuthorizationPolicy ??= new DepartmentAuthorizationPolicy(
            $this->permissionService,
            $this->tenantScopeService
        );
    }

    // Assembles all policies into a single AuthorizationService.
    // Core policies are registered first; module-contributed policies are appended.
    // Each ability maps to exactly one policy via supports() — first match wins.
    public function createAuthorizationService(): AuthorizationService
    {
        if ($this->authorizationService !== null) {
            return $this->authorizationService;
        }

        $policies = [
            $this->createUserAuthorizationPolicy(),
            $this->createRoleAuthorizationPolicy(),
            $this->createPermissionAuthorizationPolicy(),
            $this->createOperationsAuthorizationPolicy(),
            $this->createSettingsAuthorizationPolicy(),
            $this->createTenantAuthorizationPolicy(),
            $this->createDepartmentAuthorizationPolicy(),
        ];

        // Append module-contributed authorization policies.
        if ($this->moduleRegistry !== null) {
            try {
                foreach ($this->moduleRegistry->getAuthorizationPolicies() as $policyClass) {
                    if (trim($policyClass) === '') {
                        continue;
                    }
                    $policy = $this->instantiateModulePolicy($policyClass);
                    if ($policy instanceof AuthorizationPolicyInterface) {
                        $policies[] = $policy;
                    }
                }
            } catch (\Throwable) {
                // fail-open: no module registry available in this context
            }
        }

        return $this->authorizationService = new AuthorizationService($policies);
    }

    private function instantiateModulePolicy(string $policyClass): ?AuthorizationPolicyInterface
    {
        try {
            $instance = ($this->moduleClassResolver)($policyClass);
            return $instance instanceof AuthorizationPolicyInterface ? $instance : null;
        } catch (\Throwable) {
            return null;
        }
    }
}
