<?php
namespace App\Repositories;

use App\Database\Connection;
use PDO;

class RbacRepository
{
    private Connection $db;

    public function __construct(Connection $db)
    {
        $this->db = $db;
    }

    private function tablesAvailable(): bool
    {
        return $this->db->hasColumn('rbac_roles', 'id') && $this->db->hasColumn('rbac_capabilities', 'id');
    }

    public function getRoles(bool $includeInactive = true): array
    {
        if (!$this->tablesAvailable()) return [];
        $pdo = $this->db->getPdo();
        $sql = "SELECT * FROM rbac_roles";
        if (!$includeInactive) $sql .= " WHERE is_active = 1";
        $sql .= " ORDER BY is_system DESC, title ASC";
        return $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }

    public function getRoleByKey(string $roleKey): ?array
    {
        if (!$this->tablesAvailable()) return null;
        $pdo = $this->db->getPdo();
        $stmt = $pdo->prepare("SELECT * FROM rbac_roles WHERE role_key = ? LIMIT 1");
        $stmt->execute([$roleKey]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        return $row ?: null;
    }

    public function getRoleById(int $id): ?array
    {
        if (!$this->tablesAvailable() || $id <= 0) return null;
        $pdo = $this->db->getPdo();
        $stmt = $pdo->prepare("SELECT * FROM rbac_roles WHERE id = ? LIMIT 1");
        $stmt->execute([$id]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        return $row ?: null;
    }

    public function getCapabilities(bool $includeInactive = false): array
    {
        if (!$this->tablesAvailable()) return [];
        $pdo = $this->db->getPdo();
        $sql = "SELECT * FROM rbac_capabilities";
        if (!$includeInactive) $sql .= " WHERE is_active = 1";
        $sql .= " ORDER BY category ASC, sort_order ASC, title ASC";
        return $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }

    public function getRoleCapabilitiesByRoleKey(string $roleKey): array
    {
        if (!$this->tablesAvailable()) return [];
        $pdo = $this->db->getPdo();
        $stmt = $pdo->prepare("
            SELECT c.capability_key, rc.effect
            FROM rbac_roles r
            INNER JOIN rbac_role_capabilities rc ON rc.role_id = r.id
            INNER JOIN rbac_capabilities c ON c.id = rc.capability_id
            WHERE r.role_key = ? AND c.is_active = 1
        ");
        $stmt->execute([$roleKey]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }

    public function getRoleDataScopesByRoleKey(string $roleKey): array
    {
        if (!$this->tablesAvailable()) return [];
        $pdo = $this->db->getPdo();
        $stmt = $pdo->prepare("
            SELECT s.id, s.resource_key, s.scope_type, s.meta_json
            FROM rbac_roles r
            INNER JOIN rbac_role_data_scopes s ON s.role_id = r.id
            WHERE r.role_key = ?
        ");
        $stmt->execute([$roleKey]);
        $scopes = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
        if (empty($scopes)) return [];

        $scopeIds = array_map(fn($r) => (int)$r['id'], $scopes);
        $scopeIds = array_values(array_filter($scopeIds));
        $selectedUserMap = [];
        if (!empty($scopeIds) && $this->db->hasColumn('rbac_role_scope_users', 'role_data_scope_id')) {
            $ph = implode(',', array_fill(0, count($scopeIds), '?'));
            $uStmt = $pdo->prepare("SELECT role_data_scope_id, user_id FROM rbac_role_scope_users WHERE role_data_scope_id IN ($ph)");
            $uStmt->execute($scopeIds);
            foreach (($uStmt->fetchAll(PDO::FETCH_ASSOC) ?: []) as $row) {
                $sid = (int)$row['role_data_scope_id'];
                if (!isset($selectedUserMap[$sid])) $selectedUserMap[$sid] = [];
                $selectedUserMap[$sid][] = (int)$row['user_id'];
            }
        }

        foreach ($scopes as &$scope) {
            $sid = (int)($scope['id'] ?? 0);
            $scope['selected_user_ids'] = $selectedUserMap[$sid] ?? [];
        }
        unset($scope);
        return $scopes;
    }

    public function saveRole(array $data): array
    {
        if (!$this->tablesAvailable()) {
            throw new \RuntimeException('RBAC tables are not available');
        }

        $pdo = $this->db->getPdo();
        $id = (int)($data['id'] ?? 0);
        $roleKey = trim((string)($data['role_key'] ?? ''));
        $title = trim((string)($data['title'] ?? ''));
        $isActive = isset($data['is_active']) ? (int)$data['is_active'] : 1;
        $templateFrom = trim((string)($data['template_from_role_key'] ?? ''));
        $metaJson = $data['meta_json'] ?? null;

        if ($roleKey === '' || $title === '') {
            throw new \InvalidArgumentException('role_key و title الزامی است');
        }

        if ($id > 0) {
            $current = $this->getRoleById($id);
            if (!$current) {
                throw new \RuntimeException('نقش یافت نشد');
            }
            if ((int)($current['is_system'] ?? 0) === 1 && $current['role_key'] !== $roleKey) {
                throw new \RuntimeException('کلید نقش سیستمی قابل تغییر نیست');
            }
            $stmt = $pdo->prepare("
                UPDATE rbac_roles
                SET role_key = ?, title = ?, is_active = ?, template_from_role_key = ?, meta_json = ?, updated_at = NOW()
                WHERE id = ?
            ");
            $stmt->execute([$roleKey, $title, $isActive ? 1 : 0, ($templateFrom !== '' ? $templateFrom : null), $metaJson, $id]);
            return $this->getRoleById($id) ?: [];
        }

        $stmt = $pdo->prepare("
            INSERT INTO rbac_roles (role_key, title, is_system, is_active, template_from_role_key, meta_json, created_at, updated_at)
            VALUES (?, ?, 0, ?, ?, ?, NOW(), NOW())
        ");
        $stmt->execute([$roleKey, $title, $isActive ? 1 : 0, ($templateFrom !== '' ? $templateFrom : null), $metaJson]);
        return $this->getRoleById((int)$pdo->lastInsertId()) ?: [];
    }

    public function cloneRoleFromTemplate(string $newRoleKey, string $newTitle, string $templateRoleKey): array
    {
        if (!$this->tablesAvailable()) {
            throw new \RuntimeException('RBAC tables are not available');
        }

        $pdo = $this->db->getPdo();
        $newRoleKey = trim($newRoleKey);
        $newTitle = trim($newTitle);
        $templateRoleKey = trim($templateRoleKey);
        if ($newRoleKey === '' || $newTitle === '' || $templateRoleKey === '') {
            throw new \InvalidArgumentException('new_role_key/new_title/template_role_key الزامی است');
        }

        $template = $this->getRoleByKey($templateRoleKey);
        if (!$template) {
            throw new \RuntimeException('نقش تمپلیت یافت نشد');
        }

        $pdo->beginTransaction();
        try {
            $created = $this->saveRole([
                'role_key' => $newRoleKey,
                'title' => $newTitle,
                'is_active' => 1,
                'template_from_role_key' => $templateRoleKey
            ]);
            $newRoleId = (int)($created['id'] ?? 0);
            $templateRoleId = (int)($template['id'] ?? 0);

            if ($newRoleId <= 0 || $templateRoleId <= 0) {
                throw new \RuntimeException('خطا در ایجاد نقش جدید از تمپلیت');
            }

            $pdo->prepare("
                INSERT INTO rbac_role_capabilities (role_id, capability_id, effect, created_at)
                SELECT ?, capability_id, effect, NOW()
                FROM rbac_role_capabilities
                WHERE role_id = ?
                ON DUPLICATE KEY UPDATE effect = VALUES(effect)
            ")->execute([$newRoleId, $templateRoleId]);

            $scopeRows = $pdo->prepare("SELECT resource_key, scope_type, meta_json FROM rbac_role_data_scopes WHERE role_id = ?");
            $scopeRows->execute([$templateRoleId]);
            $scopes = $scopeRows->fetchAll(PDO::FETCH_ASSOC) ?: [];
            $insScope = $pdo->prepare("
                INSERT INTO rbac_role_data_scopes (role_id, resource_key, scope_type, meta_json, created_at, updated_at)
                VALUES (?, ?, ?, ?, NOW(), NOW())
            ");
            $selScopeUsers = $pdo->prepare("
                SELECT rsu.user_id
                FROM rbac_role_scope_users rsu
                INNER JOIN rbac_role_data_scopes rds ON rds.id = rsu.role_data_scope_id
                WHERE rds.role_id = ? AND rds.resource_key = ?
            ");
            $insScopeUser = $pdo->prepare("INSERT IGNORE INTO rbac_role_scope_users (role_data_scope_id, user_id, created_at) VALUES (?, ?, NOW())");
            foreach ($scopes as $scope) {
                $insScope->execute([$newRoleId, $scope['resource_key'], $scope['scope_type'], $scope['meta_json'] ?? null]);
                $newScopeId = (int)$pdo->lastInsertId();
                if ($newScopeId > 0) {
                    $selScopeUsers->execute([$templateRoleId, $scope['resource_key']]);
                    foreach (($selScopeUsers->fetchAll(PDO::FETCH_COLUMN) ?: []) as $uid) {
                        $insScopeUser->execute([$newScopeId, (int)$uid]);
                    }
                }
            }

            $pdo->commit();
            return $this->getRoleById($newRoleId) ?: [];
        } catch (\Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            throw $e;
        }
    }

    public function saveRoleCapabilitiesByRoleKey(string $roleKey, array $capabilityKeys): void
    {
        if (!$this->tablesAvailable()) {
            throw new \RuntimeException('RBAC tables are not available');
        }
        $role = $this->getRoleByKey($roleKey);
        if (!$role) {
            throw new \RuntimeException('نقش یافت نشد');
        }
        $roleId = (int)$role['id'];
        if ($roleId <= 0) {
            throw new \RuntimeException('شناسه نقش نامعتبر است');
        }

        $capabilityKeys = array_values(array_unique(array_filter(array_map(fn($v) => trim((string)$v), $capabilityKeys))));
        $pdo = $this->db->getPdo();

        $pdo->beginTransaction();
        try {
            $deleteStmt = $pdo->prepare("DELETE FROM rbac_role_capabilities WHERE role_id = ?");
            $deleteStmt->execute([$roleId]);

            if (!empty($capabilityKeys)) {
                $ph = implode(',', array_fill(0, count($capabilityKeys), '?'));
                $stmt = $pdo->prepare("SELECT id, capability_key FROM rbac_capabilities WHERE capability_key IN ($ph)");
                $stmt->execute($capabilityKeys);
                $rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
                $capMap = [];
                foreach ($rows as $r) $capMap[(string)$r['capability_key']] = (int)$r['id'];
                $ins = $pdo->prepare("INSERT INTO rbac_role_capabilities (role_id, capability_id, effect, created_at) VALUES (?, ?, 'allow', NOW())");
                foreach ($capabilityKeys as $key) {
                    if (empty($capMap[$key])) continue;
                    $ins->execute([$roleId, $capMap[$key]]);
                }
            }

            $pdo->commit();
        } catch (\Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            throw $e;
        }
    }

    public function saveRoleDataScopesByRoleKey(string $roleKey, array $scopes): void
    {
        if (!$this->tablesAvailable()) {
            throw new \RuntimeException('RBAC tables are not available');
        }
        $role = $this->getRoleByKey($roleKey);
        if (!$role) throw new \RuntimeException('نقش یافت نشد');
        $roleId = (int)$role['id'];
        if ($roleId <= 0) throw new \RuntimeException('شناسه نقش نامعتبر است');

        $pdo = $this->db->getPdo();
        $pdo->beginTransaction();
        try {
            $existingScopesStmt = $pdo->prepare("SELECT id FROM rbac_role_data_scopes WHERE role_id = ?");
            $existingScopesStmt->execute([$roleId]);
            $existingScopeIds = array_map('intval', $existingScopesStmt->fetchAll(PDO::FETCH_COLUMN) ?: []);

            if (!empty($existingScopeIds)) {
                $ph = implode(',', array_fill(0, count($existingScopeIds), '?'));
                $delUsers = $pdo->prepare("DELETE FROM rbac_role_scope_users WHERE role_data_scope_id IN ($ph)");
                $delUsers->execute($existingScopeIds);
            }
            $pdo->prepare("DELETE FROM rbac_role_data_scopes WHERE role_id = ?")->execute([$roleId]);

            $insScope = $pdo->prepare("
                INSERT INTO rbac_role_data_scopes (role_id, resource_key, scope_type, meta_json, created_at, updated_at)
                VALUES (?, ?, ?, ?, NOW(), NOW())
            ");
            $insScopeUser = $pdo->prepare("INSERT IGNORE INTO rbac_role_scope_users (role_data_scope_id, user_id, created_at) VALUES (?, ?, NOW())");

            foreach ($scopes as $scope) {
                if (!is_array($scope)) continue;
                $resourceKey = trim((string)($scope['resource_key'] ?? ''));
                $scopeType = trim((string)($scope['scope_type'] ?? 'self'));
                if ($resourceKey === '') continue;
                if (!in_array($scopeType, ['self', 'all', 'selected_users'], true)) {
                    $scopeType = 'self';
                }
                $metaJson = null;
                if (isset($scope['meta_json'])) {
                    $metaJson = is_string($scope['meta_json']) ? $scope['meta_json'] : json_encode($scope['meta_json'], JSON_UNESCAPED_UNICODE);
                }
                $insScope->execute([$roleId, $resourceKey, $scopeType, $metaJson]);
                $scopeId = (int)$pdo->lastInsertId();
                if ($scopeId <= 0) continue;
                $selectedUserIds = array_values(array_unique(array_filter(array_map('intval', (array)($scope['selected_user_ids'] ?? [])))));
                if ($scopeType === 'selected_users' && !empty($selectedUserIds)) {
                    foreach ($selectedUserIds as $uid) {
                        if ($uid > 0) $insScopeUser->execute([$scopeId, $uid]);
                    }
                }
            }

            $pdo->commit();
        } catch (\Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            throw $e;
        }
    }
}
