<?php

declare(strict_types=1);

namespace Flowaxy\Support\Managers;

use Flowaxy\Support\Facades\Log;
use Flowaxy\Support\Helpers\DatabaseHelper;
use PDO;
use Throwable;

// Менеджер ролей та прав доступу
final class RoleManager
{
    private static ?self $instance = null;
    private ?PDO $db = null;
    // @var array<int, array<int, array<string, mixed>>>
    private array $userRolesCache = [];
    // @var array<int, array<int, string>>
    private array $userPermissionsCache = [];
    // @var array<int, array<int, string>>
    private array $rolePermissionsCache = [];

    private function __construct()
    {
        $this->db = DatabaseHelper::getConnection();
    }

    // Отримання екземпляра (Singleton)
    public static function getInstance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    // Перевірка наявності дозволу у користувача
    public function hasPermission(int $userId, string $permission): bool
    {
        // Перевіряємо кеш
        if (isset($this->userPermissionsCache[$userId])) {
            return in_array($permission, $this->userPermissionsCache[$userId]);
        }

        // Завантажуємо всі дозволи користувача
        $permissions = $this->getUserPermissions($userId);
        $this->userPermissionsCache[$userId] = $permissions;

        return in_array($permission, $permissions);
    }

    // Перевірка наявності ролі у користувача
    public function hasRole(int $userId, string $roleSlug): bool
    {
        $roles = $this->getUserRoles($userId);
        foreach ($roles as $role) {
            if ($role['slug'] === $roleSlug) {
                return true;
            }
        }

        return false;
    }

    // Отримання всіх дозволів користувача
    public function getUserPermissions(int $userId): array
    {
        if ($this->db === null) {
            return [];
        }

        try {
            // Отримуємо role_ids з users
            $user = DatabaseHelper::fetchOne('SELECT role_ids FROM users WHERE id = ?', [$userId]);

            if (! $user || empty($user['role_ids'])) {
                return [];
            }

            $roleIds = json_decode($user['role_ids'], true);
            if (! is_array($roleIds) || empty($roleIds)) {
                return [];
            }

            // Отримуємо дозволи для всіх ролей користувача
            $placeholders = implode(',', array_fill(0, count($roleIds), '?'));
            $permissionsRows = DatabaseHelper::fetchAll("
                SELECT permissions
                FROM roles
                WHERE id IN ($placeholders)
            ", $roleIds);
            $permissionsRows = array_column($permissionsRows, 'permissions');

            // Збираємо всі ID прав з JSON
            $allPermissionIds = [];
            foreach ($permissionsRows as $permissionsJson) {
                if (!empty($permissionsJson)) {
                    $permissionIds = json_decode($permissionsJson, true) ?: [];
                    $allPermissionIds = array_merge($allPermissionIds, $permissionIds);
                }
            }

            // Отримуємо slug прав
            $result = [];
            if (!empty($allPermissionIds)) {
                $allPermissionIds = array_unique($allPermissionIds);
                $permPlaceholders = implode(',', array_fill(0, count($allPermissionIds), '?'));
                $permRows = DatabaseHelper::fetchAll("SELECT slug FROM permissions WHERE id IN ($permPlaceholders)", $allPermissionIds);
                $result = array_column($permRows, 'slug');
            }

            return $result ?: [];
        } catch (Throwable $e) {
            try {
                Log::Error('RoleManager getUserPermissions error', ['exception' => $e]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return [];
        }
    }

    // Отримання всіх ролей користувача
    public function getUserRoles(int $userId): array
    {
        // Перевіряємо кеш
        if (isset($this->userRolesCache[$userId])) {
            return $this->userRolesCache[$userId];
        }

        if ($this->db === null) {
            return [];
        }

        try {
            // Отримуємо role_ids з users
            $user = DatabaseHelper::fetchOne('SELECT role_ids FROM users WHERE id = ?', [$userId]);

            if (! $user || empty($user['role_ids'])) {
                $this->userRolesCache[$userId] = [];

                return [];
            }

            $roleIds = json_decode($user['role_ids'], true);
            if (! is_array($roleIds) || empty($roleIds)) {
                $this->userRolesCache[$userId] = [];

                return [];
            }

            // Отримуємо ролі по ID
            $placeholders = implode(',', array_fill(0, count($roleIds), '?'));
            $roles = DatabaseHelper::fetchAll("
                SELECT id, name, slug, description, is_system
                FROM roles
                WHERE id IN ($placeholders)
                ORDER BY name
            ", $roleIds);
            $this->userRolesCache[$userId] = $roles ?: [];

            return $this->userRolesCache[$userId];
        } catch (Throwable $e) {
            try {
                Log::Error('RoleManager getUserRoles error', ['exception' => $e]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return [];
        }
    }

    // Призначення ролі користувачу
    public function assignRole(int $userId, int $roleId): bool
    {
        if ($this->db === null) {
            return false;
        }

        try {
            // Отримуємо поточні role_ids
            $user = DatabaseHelper::fetchOne('SELECT role_ids FROM users WHERE id = ?', [$userId]);

            $roleIds = [];
            if ($user && ! empty($user['role_ids'])) {
                $roleIds = json_decode($user['role_ids'], true) ?: [];
            }

            // Добавляем новую роль, если её ещё нет
            if (! in_array($roleId, $roleIds)) {
                $roleIds[] = $roleId;
                $roleIdsJson = json_encode($roleIds);

                $stmt = DatabaseHelper::query('UPDATE users SET role_ids = ? WHERE id = ?', [$roleIdsJson, $userId]);
                if ($stmt === false) {
                    return false;
                }
            }

            // Очищаємо кеш
            unset($this->userRolesCache[$userId]);
            unset($this->userPermissionsCache[$userId]);

            return true;
        } catch (Throwable $e) {
            try {
                Log::Error('RoleManager assignRole error', ['exception' => $e]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return false;
        }
    }

    // Видалення ролі у користувача
    public function removeRole(int $userId, int $roleId): bool
    {
        if ($this->db === null) {
            return false;
        }

        try {
            // Отримуємо поточні role_ids
            $user = DatabaseHelper::fetchOne('SELECT role_ids FROM users WHERE id = ?', [$userId]);

            if (! $user || empty($user['role_ids'])) {
                return true; // Ролей немає, нічого робити не потрібно
            }

            $roleIds = json_decode($user['role_ids'], true) ?: [];

            // Видаляємо роль з масиву
            $roleIds = array_filter($roleIds, function ($id) use ($roleId) {
                return (int)$id !== $roleId;
            });
            $roleIds = array_values($roleIds); // Переіндексуємо масив

            $roleIdsJson = json_encode($roleIds);
            $stmt = DatabaseHelper::query('UPDATE users SET role_ids = ? WHERE id = ?', [$roleIdsJson, $userId]);
            if ($stmt === false) {
                return false;
            }

            // Очищаємо кеш
            unset($this->userRolesCache[$userId]);
            unset($this->userPermissionsCache[$userId]);

            return true;
        } catch (Throwable $e) {
            try {
                Log::Error('RoleManager removeRole error', ['exception' => $e]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return false;
        }
    }

    // Отримання всіх ролей
    public function getAllRoles(): array
    {
        if ($this->db === null) {
            return [];
        }

        try {
            return DatabaseHelper::fetchAll('
                SELECT id, name, slug, description, is_system, created_at, updated_at
                FROM roles
                ORDER BY name
            ') ?: [];
        } catch (Throwable $e) {
            try {
                Log::Error('RoleManager getAllRoles error', ['exception' => $e]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return [];
        }
    }

    // Отримання всіх дозволів
    public function getAllPermissions(?string $category = null): array
    {
        if ($this->db === null) {
            return [];
        }

        try {
            $sql = '
                SELECT id, name, slug, description, category, created_at, updated_at
                FROM permissions
            ';

            if ($category !== null) {
                $sql .= ' WHERE category = ?';
                return DatabaseHelper::fetchAll($sql, [$category]) ?: [];
            } else {
                return DatabaseHelper::fetchAll($sql) ?: [];
            }
        } catch (Throwable $e) {
            try {
                Log::Error('RoleManager getAllPermissions error', ['exception' => $e]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return [];
        }
    }

    // Створення нової ролі
    public function createRole(string $name, string $slug, ?string $description = null, bool $isSystem = false): ?int
    {
        if ($this->db === null) {
            return null;
        }

        try {
            $stmt = DatabaseHelper::query('
                INSERT INTO roles (name, slug, description, is_system)
                VALUES (?, ?, ?, ?)
            ', [$name, $slug, $description, $isSystem ? 1 : 0]);
            if ($stmt === false) {
                return null;
            }

            // Получаем ID последней вставленной записи
            $db = DatabaseHelper::getConnection();
            return $db ? (int)$db->lastInsertId() : null;
        } catch (Throwable $e) {
            try {
                Log::Error('RoleManager createRole error', ['exception' => $e]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return null;
        }
    }

    // Створення нового дозволу
    public function createPermission(string $name, string $slug, ?string $description = null, ?string $category = null): ?int
    {
        if ($this->db === null) {
            return null;
        }

        try {
            $stmt = DatabaseHelper::query('
                INSERT INTO permissions (name, slug, description, category)
                VALUES (?, ?, ?, ?)
            ', [$name, $slug, $description, $category]);
            if ($stmt === false) {
                return null;
            }

            // Получаем ID последней вставленной записи
            $db = DatabaseHelper::getConnection();
            return $db ? (int)$db->lastInsertId() : null;
        } catch (Throwable $e) {
            try {
                Log::Error('RoleManager createPermission error', ['exception' => $e]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return null;
        }
    }

    // Призначення дозволу ролі
    public function assignPermissionToRole(int $roleId, int $permissionId): bool
    {
        if ($this->db === null) {
            return false;
        }

        try {
            // Отримуємо поточні права ролі
            $role = DatabaseHelper::fetchOne('SELECT permissions FROM roles WHERE id = ?', [$roleId]);
            $permissionsJson = $role['permissions'] ?? null;
            $permissionIds = [];
            if (!empty($permissionsJson)) {
                $permissionIds = json_decode($permissionsJson, true) ?: [];
            }

            // Додаємо новий дозвіл, якщо його ще немає
            if (!in_array($permissionId, $permissionIds)) {
                $permissionIds[] = $permissionId;
                $permissionsJson = json_encode($permissionIds);
                DatabaseHelper::query('UPDATE roles SET permissions = ? WHERE id = ?', [$permissionsJson, $roleId]);
            }

            // Очищаємо кеш дозволів ролі
            unset($this->rolePermissionsCache[$roleId]);

            return true;
        } catch (Throwable $e) {
            try {
                Log::Error('RoleManager assignPermissionToRole error', ['exception' => $e]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return false;
        }
    }

    // Видалення дозволу у ролі
    public function removePermissionFromRole(int $roleId, int $permissionId): bool
    {
        if ($this->db === null) {
            return false;
        }

        try {
            // Отримуємо поточні права ролі
            $role = DatabaseHelper::fetchOne('SELECT permissions FROM roles WHERE id = ?', [$roleId]);
            $permissionsJson = $role['permissions'] ?? null;
            $permissionIds = [];
            if (!empty($permissionsJson)) {
                $permissionIds = json_decode($permissionsJson, true) ?: [];
            }

            // Видаляємо дозвіл з масиву
            $permissionIds = array_values(array_filter($permissionIds, function($id) use ($permissionId) {
                return $id != $permissionId;
            }));

            $permissionsJson = json_encode($permissionIds);
            DatabaseHelper::query('UPDATE roles SET permissions = ? WHERE id = ?', [$permissionsJson, $roleId]);

            // Очищаємо кеш дозволів ролі
            unset($this->rolePermissionsCache[$roleId]);

            return true;
        } catch (Throwable $e) {
            try {
                Log::Error('RoleManager removePermissionFromRole error', ['exception' => $e]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return false;
        }
    }

    // Отримання дозволів ролі
    public function getRolePermissions(int $roleId): array
    {
        // Перевіряємо кеш
        if (isset($this->rolePermissionsCache[$roleId])) {
            return $this->rolePermissionsCache[$roleId];
        }

        if ($this->db === null) {
            return [];
        }

        try {
            // Отримуємо права з JSON стовпця
            $role = DatabaseHelper::fetchOne('SELECT permissions FROM roles WHERE id = ?', [$roleId]);
            $permissionsJson = $role['permissions'] ?? null;
            $permissionIds = [];
            if (!empty($permissionsJson)) {
                $permissionIds = json_decode($permissionsJson, true) ?: [];
            }

            if (empty($permissionIds)) {
                return [];
            }

            $placeholders = implode(',', array_fill(0, count($permissionIds), '?'));
            $permissions = DatabaseHelper::fetchAll("
                SELECT p.id, p.name, p.slug, p.description, p.category
                FROM permissions p
                WHERE p.id IN ($placeholders)
                ORDER BY p.category, p.name
            ", $permissionIds);
            $this->rolePermissionsCache[$roleId] = $permissions ?: [];

            return $this->rolePermissionsCache[$roleId];
        } catch (Throwable $e) {
            try {
                Log::Error('RoleManager getRolePermissions error', ['exception' => $e]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return [];
        }
    }

    // Видалення ролі
    public function deleteRole(int $roleId): bool
    {
        if ($this->db === null) {
            return false;
        }

        try {
            // Системные роли больше не защищены от удаления
            // Root пользователь (userId = 0) имеет полный доступ без ролей
            // Администратор может удалять любые роли, включая системные
            $stmt = DatabaseHelper::query('DELETE FROM roles WHERE id = ?', [$roleId]);
            if ($stmt === false) {
                return false;
            }

            // Очищаємо кеш
            $this->userRolesCache = [];
            $this->userPermissionsCache = [];
            $this->rolePermissionsCache = [];

            return true;
        } catch (Throwable $e) {
            try {
                Log::Error('RoleManager deleteRole error', ['exception' => $e]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return false;
        }
    }

    // Очищення кешу користувача
    public function clearUserCache(?int $userId = null): void
    {
        if ($userId !== null) {
            unset($this->userRolesCache[$userId]);
            unset($this->userPermissionsCache[$userId]);
        } else {
            $this->userRolesCache = [];
            $this->userPermissionsCache = [];
        }
    }

    // Очищення кешу ролей
    public function clearRoleCache(?int $roleId = null): void
    {
        if ($roleId !== null) {
            unset($this->rolePermissionsCache[$roleId]);
        } else {
            $this->rolePermissionsCache = [];
        }
    }
}
