<?php

namespace MintyPHP\Service\Tenant;

use MintyPHP\Repository\Org\DepartmentRepositoryInterface;
use MintyPHP\Repository\Tenant\TenantRepositoryInterface;
use MintyPHP\Repository\Tenant\UserTenantRepositoryInterface;
use MintyPHP\Service\Access\PermissionService;

/**
 * Enforces tenant-boundary access control across the application.
 *
 * Core rules:
 *   - Users with TENANT_SCOPE_GLOBAL permission bypass all tenant checks.
 *   - canAccess() resolves the resource's tenant IDs, intersects with the user's
 *     tenant IDs, and grants access only on overlap.
 *   - In strict mode (default): unscoped resources (no tenant assigned) are denied.
 *   - In permissive mode: unscoped resources are allowed.
 *   - Resources whose tenants all became inactive are always denied (not treated as unscoped).
 *   - mergeTenantIdsPreservingOutOfScope() ensures that a scoped admin cannot
 *     accidentally remove tenant assignments outside their own scope.
 */
class TenantScopeService
{
    public function __construct(
        private readonly TenantRepositoryInterface $tenantRepository,
        private readonly DepartmentRepositoryInterface $departmentRepository,
        private readonly UserTenantRepositoryInterface $userTenantRepository,
        private readonly PermissionService $permissionService
    ) {
    }

    // Strict mode is the default — permissive mode must be explicitly enabled via the constant.
    public function isStrict(): bool
    {
        return defined('TENANT_SCOPE_STRICT') ? (bool) TENANT_SCOPE_STRICT : true;
    }

    public function getUserTenantIds(int $userId, bool $onlyActive = true): array
    {
        if ($userId <= 0) {
            return [];
        }

        if ($this->canBypass($userId)) {
            $ids = $this->tenantRepository->listIds();
        } else {
            $ids = array_values(array_unique(array_map(
                'intval',
                $this->userTenantRepository->listTenantIdsByUserId($userId)
            )));
        }

        if ($onlyActive) {
            return $this->filterActiveTenantIds($ids);
        }

        return $ids;
    }

    public function canAccess(string $resource, int $resourceId, int $userId): bool
    {
        if ($resourceId <= 0 || $userId <= 0) {
            return false;
        }

        if ($this->canBypass($userId)) {
            return true;
        }

        $resourceTenantIdsRaw = $this->getResourceTenantIds($resource, $resourceId);
        $userTenantIdsRaw = $this->getUserTenantIds($userId, false);
        $resourceTenantIds = $this->filterActiveTenantIds($resourceTenantIdsRaw);
        $userTenantIds = $this->filterActiveTenantIds($userTenantIdsRaw);

        // Resource had tenants, but all are inactive — deny access rather than treating it as unscoped.
        if ($resourceTenantIdsRaw && !$resourceTenantIds) {
            return false;
        }
        // Resource has no tenant assignment — allow in permissive mode, deny in strict mode.
        if (!$resourceTenantIds) {
            return $this->isStrict() ? false : true;
        }
        if (!$userTenantIds) {
            return false;
        }

        return (bool) array_intersect($resourceTenantIds, $userTenantIds);
    }

    public function filterTenantIdsForUser(array $tenantIds, int $userId): array
    {
        $tenantIds = toIntIds($tenantIds);
        $userTenantIds = $this->getUserTenantIds($userId);
        if (!$userTenantIds) {
            return [];
        }

        return array_values(array_intersect($tenantIds, $userTenantIds));
    }

    public function mergeTenantIdsPreservingOutOfScope(
        array $requestedTenantIds,
        array $existingTenantIds,
        array $allowedTenantIds
    ): array {
        $requestedTenantIds = toIntIds($requestedTenantIds);
        $existingTenantIds = toIntIds($existingTenantIds);
        $allowedTenantIds = toIntIds($allowedTenantIds);

        if (!$allowedTenantIds) {
            return $existingTenantIds;
        }

        $outOfScope = array_values(array_diff($existingTenantIds, $allowedTenantIds));
        return array_values(array_unique(array_merge($requestedTenantIds, $outOfScope)));
    }

    public function hasGlobalAccess(int $userId): bool
    {
        return $this->canBypass($userId);
    }

    public function resourceBelongsToTenant(string $resource, int $resourceId, int $tenantId): bool
    {
        if ($resourceId <= 0 || $tenantId <= 0) {
            return false;
        }

        $resourceTenantIds = $this->getResourceTenantIds($resource, $resourceId);
        if (!$resourceTenantIds) {
            return false;
        }

        $activeResourceTenantIds = $this->filterActiveTenantIds($resourceTenantIds);
        if (!$activeResourceTenantIds) {
            return false;
        }

        return in_array($tenantId, $activeResourceTenantIds, true);
    }

    private function getResourceTenantIds(string $resource, int $resourceId): array
    {
        $resource = strtolower(trim($resource));
        if ($resource === 'tenants') {
            return $resourceId > 0 ? [$resourceId] : [];
        }

        if ($resource === 'users') {
            return array_values(array_unique(array_map(
                'intval',
                $this->userTenantRepository->listTenantIdsByUserId($resourceId)
            )));
        }

        if ($resource === 'departments') {
            $department = $this->departmentRepository->find($resourceId);
            $tenantId = (int) ($department['tenant_id'] ?? 0);
            return $tenantId > 0 ? [$tenantId] : [];
        }

        return [];
    }

    private function filterActiveTenantIds(array $tenantIds): array
    {
        $tenantIds = toIntIds($tenantIds);
        if (!$tenantIds) {
            return [];
        }

        return $this->tenantRepository->listActiveIdsByIds($tenantIds);
    }

    private function canBypass(int $userId): bool
    {
        if ($userId <= 0) {
            return false;
        }

        return $this->permissionService->userHas($userId, PermissionService::TENANT_SCOPE_GLOBAL);
    }
}
