<?php

declare(strict_types=1);

namespace Flowaxy\Infrastructure\Persistence\Repositories;

use Flowaxy\Infrastructure\Persistence\Database\Database;
use Flowaxy\Domain\User\Entities\AdminRole;
use Flowaxy\Domain\User\Entities\Role;
use Flowaxy\Domain\User\Services\AdminRoleRepositoryInterface;
use Flowaxy\Support\Helpers\DatabaseHelper;
use Flowaxy\Support\Facades\Log;
use PDO;
use Exception;
use Throwable;

final class AdminRoleRepository implements AdminRoleRepositoryInterface
{
    private ?PDO $connection = null;

    public function __construct()
    {
        // ВАЖЛИВО: при status=false DB_* константи НЕ визначаються.
        // У такому режимі Root має працювати без БД, тому репозиторій не повинен
        // навіть намагатися підключатися до БД у конструкторі.
        if (!defined('DB_HOST') || !defined('DB_NAME') || DB_NAME === '') {
            $this->connection = null;
            return;
        }

        try {
            // Отримуємо підключення ТІЛЬКИ через DatabaseHelper (він коректно повертає null, якщо БД недоступна)
            $this->connection = DatabaseHelper::getConnection(false);
        } catch (Throwable $e) {
            // Логуємо лише для діагностики, але не валимо систему
            try {
                Log::Warning('AdminRoleRepository: DB connection failed (logged only)', ['exception' => $e]);
            } catch (Throwable) {
                // Ignore logging errors
            }
            $this->connection = null;
        }
    }

    public function findById(int $id): ?AdminRole
    {
        if ($this->connection === null) {
            return null;
        }

        try {
            $stmt = $this->connection->prepare('SELECT id, name, slug, description, is_system FROM roles WHERE id = ? LIMIT 1');
            $stmt->execute([$id]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!is_array($row) || empty($row)) {
                return null;
            }

            return new AdminRole(
                id: (int) $row['id'],
                name: (string) $row['name'],
                slug: (string) $row['slug'],
                description: $row['description'] ?? null,
                isSystem: (bool) $row['is_system'],
                permissions: $this->getRolePermissionSlugs((int) $row['id'])
            );
        } catch (Throwable $e) {
            try {
                Log::Error('AdminRoleRepository::findById error', ['id' => $id, 'exception' => $e]);
            } catch (Throwable) {
                // ignore
            }
            return null;
        }
    }

    public function findByName(string $name): ?AdminRole
    {
        if ($this->connection === null) {
            return null;
        }

        try {
            $stmt = $this->connection->prepare('SELECT id, name, slug, description, is_system FROM roles WHERE name = ? LIMIT 1');
            $stmt->execute([$name]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!is_array($row) || empty($row)) {
                return null;
            }

            return new AdminRole(
                id: (int) $row['id'],
                name: (string) $row['name'],
                slug: (string) $row['slug'],
                description: $row['description'] ?? null,
                isSystem: (bool) $row['is_system'],
                permissions: $this->getRolePermissionSlugs((int) $row['id'])
            );
        } catch (Throwable $e) {
            try {
                Log::Error('AdminRoleRepository::findByName error', ['name' => $name, 'exception' => $e]);
            } catch (Throwable) {
                // ignore
            }
            return null;
        }
    }

    public function create(array $data): AdminRole
    {
        // Контракт вимагає AdminRole, тому при відсутній БД повертаємо "порожню" роль і логуємо.
        if ($this->connection === null) {
            try {
                Log::Warning('AdminRoleRepository::create: DB connection not available', ['data' => $data]);
            } catch (Throwable) {
                // ignore
            }

            return new AdminRole(
                id: 0,
                name: (string)($data['name'] ?? ''),
                slug: (string)($data['slug'] ?? ''),
                description: isset($data['description']) ? (string)$data['description'] : null,
                isSystem: (bool)($data['is_system'] ?? false),
                permissions: (array)($data['permissions'] ?? [])
            );
        }

        try {
            $name = (string)($data['name'] ?? '');
            $slug = (string)($data['slug'] ?? '');
            $description = $data['description'] ?? null;
            $isSystem = (bool)($data['is_system'] ?? false);

            $stmt = $this->connection->prepare('INSERT INTO roles (name, slug, description, is_system) VALUES (?, ?, ?, ?)');
            $stmt->execute([$name, $slug, $description, $isSystem ? 1 : 0]);

            $id = (int) $this->connection->lastInsertId();

            return new AdminRole(
                id: $id,
                name: $name,
                slug: $slug,
                description: $description !== null ? (string)$description : null,
                isSystem: $isSystem,
                permissions: $this->getRolePermissionSlugs($id)
            );
        } catch (Throwable $e) {
            try {
                Log::Error('AdminRoleRepository::create error', ['data' => $data, 'exception' => $e]);
            } catch (Throwable) {
                // ignore
            }

            // Мінімальний fallback без UI-крашу
            return new AdminRole(
                id: 0,
                name: (string)($data['name'] ?? ''),
                slug: (string)($data['slug'] ?? ''),
                description: isset($data['description']) ? (string)$data['description'] : null,
                isSystem: (bool)($data['is_system'] ?? false),
                permissions: (array)($data['permissions'] ?? [])
            );
        }
    }

    public function update(int $id, array $data): bool
    {
        if ($this->connection === null) {
            return false;
        }

        try {
            $current = $this->connection->prepare('SELECT id, name, slug, description, is_system FROM roles WHERE id = ? LIMIT 1');
            $current->execute([$id]);
            $row = $current->fetch(PDO::FETCH_ASSOC);
            if (!is_array($row) || empty($row)) {
                return false;
            }

            $name = array_key_exists('name', $data) ? (string)$data['name'] : (string)$row['name'];
            $slug = array_key_exists('slug', $data) ? (string)$data['slug'] : (string)$row['slug'];
            $description = array_key_exists('description', $data) ? $data['description'] : ($row['description'] ?? null);
            $isSystem = array_key_exists('is_system', $data) ? (bool)$data['is_system'] : (bool)$row['is_system'];

            $stmt = $this->connection->prepare('UPDATE roles SET name = ?, slug = ?, description = ?, is_system = ? WHERE id = ?');
            return $stmt->execute([$name, $slug, $description, $isSystem ? 1 : 0, $id]);
        } catch (Throwable $e) {
            try {
                Log::Error('AdminRoleRepository::update error', ['id' => $id, 'data' => $data, 'exception' => $e]);
            } catch (Throwable) {
                // ignore
            }
            return false;
        }
    }

    public function delete(int $id): bool
    {
        if ($this->connection === null) {
            return false;
        }

        try {
            $stmt = $this->connection->prepare('DELETE FROM roles WHERE id = ?');
            return $stmt->execute([$id]);
        } catch (Throwable $e) {
            try {
                Log::Error('AdminRoleRepository::delete error', ['id' => $id, 'exception' => $e]);
            } catch (Throwable) {
                // ignore
            }
            return false;
        }
    }

    public function findAll(): array
    {
        if ($this->connection === null) {
            return [];
        }

        try {
            $stmt = $this->connection->query('SELECT id, name, slug, description, is_system FROM roles ORDER BY name');
            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
            $result = [];
            foreach ($rows as $row) {
                if (!is_array($row) || empty($row['id'])) {
                    continue;
                }
                $roleId = (int) $row['id'];
                $result[] = new AdminRole(
                    id: $roleId,
                    name: (string) $row['name'],
                    slug: (string) $row['slug'],
                    description: $row['description'] ?? null,
                    isSystem: (bool) $row['is_system'],
                    permissions: $this->getRolePermissionSlugs($roleId)
                );
            }
            return $result;
        } catch (Throwable $e) {
            try {
                Log::Error('AdminRoleRepository::findAll error', ['exception' => $e]);
            } catch (Throwable) {
                // ignore
            }
            return [];
        }
    }

    /**
     * @return array<int, Role>
     */
    public function getRolesForUser(int $userId): array
    {
        if ($this->connection === null) {
            return [];
        }

        $roleIds = $this->getRoleIdsForUser($userId);
        if ($roleIds === []) {
            return [];
        }

        $placeholders = implode(',', array_fill(0, count($roleIds), '?'));
        $stmt = $this->connection->prepare("
            SELECT id, name, slug, description, is_system
            FROM roles
            WHERE id IN ($placeholders)
            ORDER BY name
        ");
            $stmt->execute($roleIds);
            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];

        $roles = [];
        foreach ($rows as $row) {
            $roles[] = new Role(
                id: (int)$row['id'],
                name: $row['name'],
                slug: $row['slug'],
                description: $row['description'] ?? null,
                isSystem: (bool)$row['is_system'],
                permissions: $this->getRolePermissionSlugs((int)$row['id'])
            );
        }

        return $roles;
    }

    /**
     * @return array<int, string>
     */
    public function getPermissionsForUser(int $userId): array
    {
        if ($this->connection === null) {
            Log::Debug("AdminRoleRepository::getPermissionsForUser: Connection is null", ['user_id' => $userId]);
            return [];
        }

        $roleIds = $this->getRoleIdsForUser($userId);
        Log::Debug("AdminRoleRepository::getPermissionsForUser: User roles retrieved", [
            'user_id' => $userId,
            'role_ids' => $roleIds,
        ]);

        if ($roleIds === []) {
            Log::Debug("AdminRoleRepository::getPermissionsForUser: No roles found for user", ['user_id' => $userId]);
            return [];
        }

        $placeholders = implode(',', array_fill(0, count($roleIds), '?'));
        $stmt = $this->connection->prepare("
            SELECT permissions
            FROM roles
            WHERE id IN ($placeholders)
        ");
        $stmt->execute($roleIds);
        $permissionsRows = $stmt->fetchAll(PDO::FETCH_COLUMN);

        Log::Debug("AdminRoleRepository::getPermissionsForUser: Permissions JSON from roles", [
            'user_id' => $userId,
            'permissions_json' => $permissionsRows,
        ]);

        // Збираємо всі ID прав з JSON
        $allPermissionIds = [];
        foreach ($permissionsRows as $permissionsJson) {
            if (!empty($permissionsJson)) {
                $permissionIds = json_decode($permissionsJson, true) ?: [];
                Log::Debug("AdminRoleRepository::getPermissionsForUser: Decoded permission IDs", [
                    'user_id' => $userId,
                    'permission_ids' => $permissionIds,
                ]);
                $allPermissionIds = array_merge($allPermissionIds, $permissionIds);
            }
        }

        Log::Debug("AdminRoleRepository::getPermissionsForUser: All permission IDs collected", [
            'user_id' => $userId,
            'all_permission_ids' => $allPermissionIds,
        ]);

        // Отримуємо slug прав
        $permissions = [];
        if (!empty($allPermissionIds)) {
            $allPermissionIds = array_unique($allPermissionIds);
            $permPlaceholders = implode(',', array_fill(0, count($allPermissionIds), '?'));
            $permStmt = $this->connection->prepare("SELECT slug FROM permissions WHERE id IN ($permPlaceholders)");
            $permStmt->execute($allPermissionIds);
            $permissions = $permStmt->fetchAll(PDO::FETCH_COLUMN) ?: [];
        }

        Log::Debug("AdminRoleRepository::getPermissionsForUser: Final permissions retrieved", [
            'user_id' => $userId,
            'permissions' => $permissions,
        ]);

        if (!empty($permissions)) {
            Log::Info("AdminRoleRepository::getPermissionsForUser: Permissions retrieved successfully", [
                'user_id' => $userId,
                'permission_count' => count($permissions),
            ]);
        }

        return array_values(array_filter(array_map('strval', $permissions ?: [])));
    }

    public function userHasRole(int $userId, string $roleSlug): bool
    {
        foreach ($this->getRolesForUser($userId) as $role) {
            if ($role->slug === $roleSlug) {
                return true;
            }
        }

        return false;
    }

    public function userHasPermission(int $userId, string $permission): bool
    {
        // Root пользователь (userId = 0) всегда имеет все права
        if ($userId === 0) {
            return true;
        }

        $permissions = $this->getPermissionsForUser($userId);
        $hasPermission = in_array($permission, $permissions, true);

        Log::Debug("AdminRoleRepository::userHasPermission: Permission check", [
            'user_id' => $userId,
            'permission' => $permission,
            'has_permission' => $hasPermission,
            'all_permissions' => $permissions,
        ]);

        return $hasPermission;
    }

    public function assignRole(int $userId, int $roleId): bool
    {
        if ($this->connection === null) {
            return false;
        }

        $roleIds = $this->getRoleIdsForUser($userId);
        if (in_array($roleId, $roleIds, true)) {
            return true;
        }

        $roleIds[] = $roleId;
        $stmt = $this->connection->prepare('UPDATE users SET role_ids = ? WHERE id = ?');

        return $stmt->execute([json_encode($roleIds), $userId]);
    }

    public function removeRole(int $userId, int $roleId): bool
    {
        if ($this->connection === null) {
            return false;
        }

        $roleIds = array_values(array_filter(
            $this->getRoleIdsForUser($userId),
            static fn (int $id) => $id !== $roleId
        ));

        $stmt = $this->connection->prepare('UPDATE users SET role_ids = ? WHERE id = ?');

        return $stmt->execute([json_encode($roleIds), $userId]);
    }

    /**
     * @return int[]
     */
    /**
     * @return array<int, int>
     */
    private function getRoleIdsForUser(int $userId): array
    {
        if ($this->connection === null) {
            Log::Debug("AdminRoleRepository::getRoleIdsForUser: Connection is null", ['user_id' => $userId]);
            return [];
        }

        try {
            Log::Debug("AdminRoleRepository::getRoleIdsForUser: Retrieving role IDs", ['user_id' => $userId]);

            $stmt = $this->connection->prepare('SELECT role_ids FROM users WHERE id = ?');
            $stmt->execute([$userId]);
            $user = $stmt->fetch(PDO::FETCH_ASSOC);

            if (! $user || empty($user['role_ids'])) {
                Log::Debug("AdminRoleRepository::getRoleIdsForUser: User not found or role_ids is empty", [
                    'user_id' => $userId,
                    'role_ids_value' => $user['role_ids'] ?? 'NULL',
                ]);
                return [];
            }

            Log::Debug("AdminRoleRepository::getRoleIdsForUser: Raw role_ids JSON retrieved", [
                'user_id' => $userId,
                'role_ids_json' => $user['role_ids'],
            ]);

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

            Log::Debug("AdminRoleRepository::getRoleIdsForUser: Decoded role_ids", [
                'user_id' => $userId,
                'role_ids' => $roleIds,
            ]);

            $result = array_values(array_filter(array_map('intval', is_array($roleIds) ? $roleIds : [])));

            if (!empty($result)) {
                Log::Info("AdminRoleRepository::getRoleIdsForUser: Role IDs retrieved successfully", [
                    'user_id' => $userId,
                    'role_count' => count($result),
                ]);
            }

            return $result;
        } catch (Exception $e) {
            try {
                Log::Error('Помилка отримання ролей користувача', ['exception' => $e, 'user_id' => $userId]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return [];
        }
    }

    /**
     * @return string[]
     */
    /**
     * @return array<int, string>
     */
    private function getRolePermissionSlugs(int $roleId): array
    {
        if ($this->connection === null) {
            return [];
        }

        $stmt = $this->connection->prepare('SELECT permissions FROM roles WHERE id = ?');
        $stmt->execute([$roleId]);
        $permissionsJson = $stmt->fetchColumn();

        $permissionIds = [];
        if (!empty($permissionsJson)) {
            $permissionIds = json_decode($permissionsJson, true) ?: [];
        }

        $slugs = [];
        if (!empty($permissionIds)) {
            $placeholders = implode(',', array_fill(0, count($permissionIds), '?'));
            $permStmt = $this->connection->prepare("SELECT slug FROM permissions WHERE id IN ($placeholders)");
            $permStmt->execute($permissionIds);
            $slugs = $permStmt->fetchAll(PDO::FETCH_COLUMN) ?: [];
        }

        return array_values(array_filter(array_map('strval', $slugs ?: [])));
    }
}
