<?php
namespace App\Services;

use App\Repositories\RbacRepository;

class RbacService
{
    private RbacRepository $repo;
    private array $roleCapabilityCache = [];
    private array $roleScopeCache = [];

    public function __construct(RbacRepository $repo)
    {
        $this->repo = $repo;
    }

    public function getCatalog(): array
    {
        return [
            'roles' => $this->repo->getRoles(true),
            'capabilities' => $this->repo->getCapabilities(false),
        ];
    }

    public function getRolePermissions(string $roleKey): array
    {
        return [
            'role' => $this->repo->getRoleByKey($roleKey),
            'capabilities' => $this->getAllowedCapabilitiesForRole($roleKey),
            'scopes' => $this->getDataScopesForRole($roleKey),
        ];
    }

    public function saveRole(array $data): array
    {
        $roleKey = trim((string)($data['role_key'] ?? ''));
        if ($roleKey === '') {
            throw new \InvalidArgumentException('role_key الزامی است');
        }
        if (!preg_match('/^[a-zA-Z0-9_\\-]+$/', $roleKey)) {
            throw new \InvalidArgumentException('role_key فقط باید شامل حروف/عدد/خط تیره/آندرلاین باشد');
        }
        return $this->repo->saveRole($data);
    }

    public function cloneRoleFromTemplate(string $newRoleKey, string $newTitle, string $templateRoleKey): array
    {
        if (!preg_match('/^[a-zA-Z0-9_\\-]+$/', $newRoleKey)) {
            throw new \InvalidArgumentException('new_role_key نامعتبر است');
        }
        return $this->repo->cloneRoleFromTemplate($newRoleKey, $newTitle, $templateRoleKey);
    }

    public function saveRoleCapabilities(string $roleKey, array $capabilityKeys): void
    {
        $role = $this->repo->getRoleByKey($roleKey);
        if (!$role) {
            throw new \RuntimeException('نقش یافت نشد');
        }

        $capabilityKeys = array_values(array_unique(array_filter(array_map(fn($v) => trim((string)$v), $capabilityKeys))));

        // Guardrail: admin role must keep minimum recovery capabilities
        if (($role['role_key'] ?? '') === 'admin') {
            $required = ['menu.exports', 'export.catalog'];
            foreach ($required as $cap) {
                if (!in_array($cap, $capabilityKeys, true)) {
                    $capabilityKeys[] = $cap;
                }
            }
        }

        $this->repo->saveRoleCapabilitiesByRoleKey($roleKey, $capabilityKeys);
        unset($this->roleCapabilityCache[$roleKey]);
    }

    public function saveRoleDataScopes(string $roleKey, array $scopes): void
    {
        $this->repo->saveRoleDataScopesByRoleKey($roleKey, $scopes);
        unset($this->roleScopeCache[$roleKey]);
    }

    public function getCurrentRoleKey(): string
    {
        return (string)($_SESSION['role'] ?? 'agent');
    }

    public function can(string $capabilityKey, ?string $roleKey = null): bool
    {
        $roleKey = trim((string)($roleKey ?? $this->getCurrentRoleKey()));
        if ($roleKey === '') $roleKey = 'agent';

        if ($roleKey === 'admin') {
            return true;
        }

        $allowed = $this->getAllowedCapabilitiesForRole($roleKey);
        if (!empty($allowed)) {
            return in_array($capabilityKey, $allowed, true);
        }

        return $this->fallbackCan($roleKey, $capabilityKey);
    }

    public function canExportDataset(string $dataset, ?string $roleKey = null): bool
    {
        return $this->can('export.dataset.' . trim($dataset), $roleKey);
    }

    public function canViewMenu(string $menuKey, ?string $roleKey = null): bool
    {
        return $this->can('menu.' . trim($menuKey), $roleKey);
    }

    public function getDataScope(string $resourceKey, ?string $roleKey = null): array
    {
        $roleKey = trim((string)($roleKey ?? $this->getCurrentRoleKey()));
        $scopes = $this->getDataScopesForRole($roleKey);
        foreach ($scopes as $scope) {
            if (($scope['resource_key'] ?? '') === $resourceKey) {
                return $scope;
            }
        }
        $fallback = ($roleKey === 'admin' || $roleKey === 'crm_specialist' || $roleKey === 'accountant' || $roleKey === 'system') ? 'all' : 'self';
        return ['resource_key' => $resourceKey, 'scope_type' => $fallback, 'selected_user_ids' => []];
    }

    private function getAllowedCapabilitiesForRole(string $roleKey): array
    {
        if (isset($this->roleCapabilityCache[$roleKey])) {
            return $this->roleCapabilityCache[$roleKey];
        }

        $rows = $this->repo->getRoleCapabilitiesByRoleKey($roleKey);
        $allowed = [];
        $denied = [];
        foreach ($rows as $row) {
            $key = (string)($row['capability_key'] ?? '');
            if ($key === '') continue;
            if (($row['effect'] ?? 'allow') === 'deny') {
                $denied[] = $key;
            } else {
                $allowed[] = $key;
            }
        }
        $allowed = array_values(array_unique(array_diff($allowed, $denied)));
        $this->roleCapabilityCache[$roleKey] = $allowed;
        return $allowed;
    }

    private function getDataScopesForRole(string $roleKey): array
    {
        if (isset($this->roleScopeCache[$roleKey])) {
            return $this->roleScopeCache[$roleKey];
        }
        $rows = $this->repo->getRoleDataScopesByRoleKey($roleKey);
        $this->roleScopeCache[$roleKey] = $rows;
        return $rows;
    }

    private function fallbackCan(string $roleKey, string $capabilityKey): bool
    {
        // Backward-compatible default behavior before full RBAC rollout.
        $accountant = ['menu.exports', 'export.catalog', 'export.dataset.payments'];
        $agent = [
            'menu.dashboard', 'menu.performance_dashboard', 'menu.leads', 'menu.blataklif_leads',
            'menu.deals', 'menu.legacy_deals', 'menu.persons', 'menu.reminders', 'menu.settings',
            'action.leads.edit', 'action.deals.create', 'action.deals.edit', 'action.payments.create', 'action.payments.edit',
            'performance.view', 'performance.drilldown'
        ];
        $crm = [
            'menu.dashboard', 'menu.leads', 'menu.blataklif_leads', 'menu.deals', 'menu.legacy_deals',
            'menu.referrals', 'menu.persons', 'menu.reminders', 'menu.settings', 'menu.exports',
            'export.catalog', 'export.dataset.payments', 'action.deals.edit', 'action.payments.create', 'action.payments.edit'
        ];

        if (strpos($capabilityKey, 'performance.chart.') === 0 || strpos($capabilityKey, 'performance.card.') === 0 || strpos($capabilityKey, 'performance.table.') === 0) {
            return $roleKey === 'admin' || $roleKey === 'agent';
        }
        if (strpos($capabilityKey, 'kpi.') === 0) {
            return $roleKey === 'admin';
        }

        if ($roleKey === 'accountant') {
            return in_array($capabilityKey, $accountant, true);
        }
        if ($roleKey === 'crm_specialist') {
            return in_array($capabilityKey, $crm, true);
        }
        if ($roleKey === 'agent') {
            return in_array($capabilityKey, $agent, true);
        }
        return false;
    }
}
