<?php

namespace App\Controllers\View;

use App\Models\RoleModel;
use App\Models\PermissionModel;
use App\Models\RolePermissionModel;
use App\Models\UserModel;
use App\Models\UserRoleModel;
use App\Models\ConfigurationModel;
use App\Models\StaffModel;
use CodeIgniter\Controller;

class RolePermissionController extends Controller
{
    protected $roleModel;
    protected $userModel;
    protected $userRoleModel;
    protected $staffModel;
    protected $configModel;
    protected $permissionModel;
    protected $rolePermissionModel;
    protected $request;
    protected $db;
    protected $schoolYear;
    protected $semester;

    public function __construct()
    {
        $this->roleModel = new RoleModel();
        $this->userModel = new UserModel();
        $this->userRoleModel = new UserRoleModel();
        $this->staffModel = new StaffModel();
        $this->configModel = new ConfigurationModel();
        $this->permissionModel = new PermissionModel();
        $this->rolePermissionModel = new RolePermissionModel();

        $this->request = \Config\Services::request();
        $this->db = \Config\Database::connect();
        $this->schoolYear = $this->configModel->getConfig('school_year');
        $this->semester = $this->configModel->getConfig('semester');
    }

    public function index()
    {
        helper('url');
        return view('rolepermission/list_roles', [
            'rolesEndpoint' => site_url('api/rolepermission/roles'),
        ]);
    }

    public function assignRole()
    {
        helper('url');
        return view('rolepermission/assign_role', [
            'assignRoleEndpoint' => site_url('api/rolepermission/users'),
            'rolesEndpoint'      => site_url('api/rolepermission/roles'),
        ]);
    }

    public function saveRole()
    {
        if ($this->request->getMethod(true) === 'POST') {
            $userId = (int) $this->request->getPost('user_id');
            $roleIdsRaw = $this->request->getPost('role_ids');
            $roleIdsArray = is_array($roleIdsRaw) ? $roleIdsRaw : ($roleIdsRaw !== null ? [$roleIdsRaw] : []);
            $roleIds = array_values(array_unique(array_filter(array_map('intval', $roleIdsArray), fn($id) => $id > 0)));
            $adminId = session()->get('user_id');

            if ($userId <= 0) {
                return redirect()->to('rolepermission/assign_role')->with('error', 'Invalid user selection.');
            }

            try {
                $this->db->transStart();

                // Remove all existing roles for the user
                $this->userRoleModel->where('user_id', $userId)->delete();

                // Assign new roles
                if (!empty($roleIds)) {
                    foreach ($roleIds as $roleId) {
                        $this->userRoleModel->insert([
                            'user_id' => $userId,
                            'role_id' => $roleId,
                            'created_at' => utc_now(),
                            'updated_by' => $adminId
                        ]);
                    }
                }

                $this->db->transComplete();

                if ($this->db->transStatus() === false) {
                    throw new \RuntimeException('Transaction failed.');
                }

                // ✅ Load role names after saving
                $assignedRoles = !empty($roleIds)
                    ? $this->roleModel->whereIn('id', $roleIds)->findAll()
                    : [];
                $roleNames = array_map(fn($r) => strtolower($r['name']), $assignedRoles ?? []);

                // ✅ Update staff record if needed
                $this->updateStaffRecord($userId, $roleNames);

                return redirect()->to('rolepermission/assign_role')->with('success', 'Roles updated successfully.');
            } catch (\Exception $e) {
                $this->db->transRollback();
                log_message('error', 'Error updating roles: ' . $e->getMessage());
                return redirect()->to('rolepermission/assign_role')->with('error', 'Failed to update roles.');
            }
        }

        return redirect()->to('rolepermission/assign_role')->with('error', 'Invalid request.');
    }

    public function usersData()
    {
        return $this->response->setJSON($this->buildUsersWithRolesPayload());
    }

    public function rolesData()
    {
        $rows = $this->roleModel
            ->select('roles.id, roles.name, roles.description, COUNT(DISTINCT user_roles.user_id) AS user_count')
            ->join('user_roles', 'user_roles.role_id = roles.id', 'left')
            ->groupBy('roles.id, roles.name, roles.description')
            ->orderBy('roles.name', 'ASC')
            ->findAll();

        $normalized = array_map(static function ($row) {
            if (!is_array($row)) {
                return [];
            }
            return [
                'id'          => (int) ($row['id'] ?? 0),
                'name'        => (string) ($row['name'] ?? ''),
                'description' => $row['description'] ?? null,
                'user_count'  => (int) ($row['user_count'] ?? 0),
            ];
        }, $rows ?? []);

        return $this->response->setJSON(['roles' => $normalized]);
    }

    private function buildUsersWithRolesPayload(): array
    {
        $rows = $this->userModel
            ->select('users.id, users.firstname, users.lastname, users.email, users.account_id, roles.name AS role_name')
            ->join('user_roles', 'users.id = user_roles.user_id', 'left')
            ->join('roles', 'user_roles.role_id = roles.id', 'left')
            ->orderBy('users.id', 'ASC')
            ->findAll();

        $users = [];
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }

            $id = (int) ($row['id'] ?? 0);
            if ($id <= 0) {
                continue;
            }

            if (!isset($users[$id])) {
                $users[$id] = [
                    'id'         => $id,
                    'firstname'  => $row['firstname'] ?? '',
                    'lastname'   => $row['lastname'] ?? '',
                    'email'      => $row['email'] ?? '',
                    'account_id' => $row['account_id'] ?? '',
                    'roles'      => [],
                ];
            }

            $roleName = $row['role_name'] ?? null;
            if ($roleName) {
                $users[$id]['roles'][] = $roleName;
            }
        }

        $normalized = array_map(static function ($user) {
            $roles = array_values(array_unique(array_filter($user['roles'] ?? [], 'strlen')));
            return [
                'id'         => $user['id'],
                'firstname'  => $user['firstname'] ?? '',
                'lastname'   => $user['lastname'] ?? '',
                'email'      => $user['email'] ?? '',
                'account_id' => $user['account_id'] ?? '',
                'roles'      => $roles,
            ];
        }, array_values($users));

        return ['users' => $normalized];
    }

    private function updateStaffRecord(int $userId, array $newRoleNames): void
    {
        $excludedRoles = ['parent', 'student', 'guest'];
        $loweredNewRoles = array_map('strtolower', $newRoleNames);

        $user = $this->userModel->find($userId);
        if (!$user) {
            log_message('error', "updateStaffRecord: user_id {$userId} not found");
            return;
        }

        // Fetch existing staff record if it exists
        $existingStaff = $this->staffModel->where('user_id', $userId)->first();

        // Extract existing roles from DB if any
        $existingRoles = [];
        if (!empty($existingStaff['role_name'])) {
            $existingRoles = array_map('strtolower', array_map('trim', explode(',', $existingStaff['role_name'])));
        }

        // Merge and deduplicate roles
        $allRoles = array_unique(array_merge($existingRoles, $loweredNewRoles));

        // Determine staff roles (excluding parent/student/guest)
        $staffRoles = array_filter($newRoleNames, fn($role) => !in_array($role, $excludedRoles));

        // Determine active role
        $activeRole = !empty($staffRoles) ? $staffRoles[0] : 'inactive';

        $email = $this->generateUniqueStaffEmail($user['firstname'], $user['lastname'], $userId);
        $now = utc_now();

        $row = [
            'user_id'     => $userId,
            'firstname'   => $user['firstname'],
            'lastname'    => $user['lastname'],
            'email'       => $email,
            'phone'       => $user['cellphone'],
            'role_name'   => implode(', ', $allRoles),   // historical roles
            'active_role' => $activeRole,
            'school_year' => $this->schoolYear,
            'updated_at'  => $now,
        ];

        $this->staffModel->upsert($row);
    }

    private function generateUniqueStaffEmail(string $firstname, string $lastname, int $userId): string
    {
        $domain = '@alrahmaisgl.org';

        // Sanitize names: lowercase, letters only
        $firstname = strtolower(preg_replace('/[^a-z]/i', '', $firstname));
        $lastname  = strtolower(preg_replace('/[^a-z]/i', '', $lastname));

        // Use at least 1 letter from firstname
        $min = 1;
        $max = strlen($firstname);
        $base = '';
        $email = '';

        for ($i = $min; $i <= $max; $i++) {
            $base = substr($firstname, 0, $i) . $lastname;
            $email = $base . $domain;

            // If email not taken or belongs to same user, use it
            $exists = $this->staffModel->where('email', $email)
                ->where('user_id !=', $userId)
                ->first();
            if (!$exists) {
                return $email;
            }
        }

        // If all combinations used, fallback to adding user ID
        return $base . $userId . $domain;
    }

    public function listRoles()
    {
        helper('url');
        return view('rolepermission/list_roles', [
            'rolesEndpoint' => site_url('api/rolepermission/roles'),
        ]);
    }

    public function editRolePermissions($roleId)
    {
        // Fetch roles, permissions, and role permissions data
        $roles = $this->roleModel->findAll();
        $permissions = $this->permissionModel->findAll();
        $rolePermissions = $this->rolePermissionModel->where('role_id', $roleId)->findAll();

        // Convert existing role permissions to a simple array for comparison
        $existingPermissions = [];
        foreach ($rolePermissions as $rolePermission) {
            $existingPermissions[$rolePermission['permission_id']] = [
                'can_create' => $rolePermission['can_create'],
                'can_read' => $rolePermission['can_read'],
                'can_update' => $rolePermission['can_update'],
                'can_delete' => $rolePermission['can_delete'],
            ];
        }

        // Handle POST request
        if (strtolower($this->request->getMethod()) === 'post') {
            // Get the posted permissions data
            $postPermissions = $this->request->getPost('permissions');

            // Call saveRolePermissions with the correct data
            return $this->saveRolePermissions($roleId, $postPermissions);
        }

        return view('rolepermission/edit_role_permissions', [
            'roles' => $roles,
            'roleId' => $roleId,
            'permissions' => $permissions,
            'rolePermissions' => $rolePermissions,
            'existingPermissions' => $existingPermissions
        ]);
    }

    public function saveRolePermissions($roleId = null, $permissions = null)
    {
        // If $roleId and $permissions are not passed, retrieve them from POST
        if (!$roleId) {
            $roleId = $this->request->getPost('role_id');
        }

        if (!$permissions) {
            $permissions = $this->request->getPost('permissions');
        }

        if (!$roleId) {
            return redirect()->back()->with('error', 'Role ID is missing.');
        }

        // Fetch existing permissions for the role
        $rolePermissions = $this->rolePermissionModel->where('role_id', $roleId)->findAll();

        $existingPermissions = [];
        foreach ($rolePermissions as $rolePermission) {
            $existingPermissions[$rolePermission['permission_id']] = $rolePermission;
        }

        try {
            $this->rolePermissionModel->transStart();

            // Process the new permissions if provided
            if ($permissions && is_array($permissions)) {
                foreach ($permissions as $permissionId => $crudValues) {
                    // Ensure CRUD values are correctly set, defaulting to 0 if not provided
                    $permissionData = [
                        'can_create' => isset($crudValues['create']) ? 1 : 0,
                        'can_read'   => isset($crudValues['read']) ? 1 : 0,
                        'can_update' => isset($crudValues['update']) ? 1 : 0,
                        'can_delete' => isset($crudValues['delete']) ? 1 : 0,
                        'updated_at' => utc_now(),
                    ];

                    if (isset($existingPermissions[$permissionId])) {
                        // Update existing permission
                        $this->rolePermissionModel->where('role_id', $roleId)
                            ->where('permission_id', $permissionId)
                            ->set($permissionData)
                            ->update();
                    } else {
                        // Insert new permission
                        $permissionData['role_id'] = $roleId;
                        $permissionData['permission_id'] = $permissionId;
                        $permissionData['created_at'] = utc_now();
                        $this->rolePermissionModel->insert($permissionData);
                    }
                }
            }

            // Remove permissions that are no longer present
            foreach ($existingPermissions as $permissionId => $permissionData) {
                if (!isset($permissions[$permissionId])) {
                    $this->rolePermissionModel->where('role_id', $roleId)
                        ->where('permission_id', $permissionId)
                        ->delete();
                }
            }

            $this->rolePermissionModel->transComplete();

            if ($this->rolePermissionModel->transStatus() === false) {
                throw new \RuntimeException('Transaction failed.');
            }

            return redirect()->to('/rolepermission/edit_role_permissions/' . $roleId)
                ->with('status', 'Permissions saved successfully.');
        } catch (\Exception $e) {
            $this->rolePermissionModel->transRollback();
            log_message('error', 'Failed to save permissions: ' . $e->getMessage());
            return redirect()->back()->with('error', 'Failed to save permissions: ' . $e->getMessage());
        }
    }

    // Role management functions
public function rolesList()
{
    return $this->listRoles();
}


    public function addRole()
    {
        return view('rolepermission/add_role');
    }

public function storeRole()
{
    $validation = \Config\Services::validation();

    // Validation rules
    $validation->setRules([
        'name'             => 'required|min_length[3]|max_length[255]|is_unique[roles.name]',
        'slug'             => 'permit_empty|max_length[64]|is_unique[roles.slug]',
        'description'      => 'permit_empty|max_length[500]',
        'dashboard_route'  => [
            'rules'  => 'required|min_length[1]|max_length[255]|regex_match[/^[a-z0-9_\\/\\-]+$/]',
            'errors' => [
                'regex_match' => 'Route may contain lowercase letters, numbers, /, -, and _.'
            ]
        ],
        'priority'         => 'required|integer|greater_than_equal_to[0]|less_than_equal_to[100000]',
        'is_active'        => 'required|in_list[0,1]',
    ]);

    if (!$validation->withRequest($this->request)->run()) {
        return redirect()->back()->withInput()->with('errors', $validation->getErrors());
    }

    // Normalize + sanitize inputs
    $rawName    = trim((string) $this->request->getPost('name'));
    $rawSlug    = trim((string) $this->request->getPost('slug'));
    $route      = trim((string) $this->request->getPost('dashboard_route'));
    $desc       = trim((string) $this->request->getPost('description'));
    $priority   = (int) $this->request->getPost('priority');
    $isActive   = (string) $this->request->getPost('is_active') === '1' ? 1 : 0;

    // Force lowercase for consistency
    $name             = strtolower($rawName);
    $dashboard_route  = strtolower($route);
    $description      = strtolower($desc);

    // Build slug: from input if provided, else from name
    $slug = $rawSlug !== '' ? $rawSlug : $name;
    // convert spaces / invalid chars to underscores and lowercase
    $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '_', $slug));
    $slug = trim($slug, '_');

    // Ensure slug uniqueness (in case collision after normalization)
    $slug = $this->ensureUniqueSlug($slug);

    $data = [
        'name'             => $name,
        'slug'             => $slug,
        'description'      => $description,
        'dashboard_route'  => $dashboard_route,
        'priority'         => $priority,
        'is_active'        => $isActive,
        'created_at'       => utc_now(),
        'updated_at'       => utc_now(),
    ];

    if ($this->roleModel->insert($data)) {
        return redirect()->to('rolepermission/roles')->with('success', 'Role created successfully!');
    }

    return redirect()->back()->withInput()->with('error', 'Failed to create role.');
}

/**
 * Ensure slug is unique in roles.slug; if exists, append -2, -3, ...
 */
/**
 * Ensure slug is unique in the roles table.
 * - If $ignoreId is provided, that record is excluded (for updates).
 * - Appends _2, _3, ... if duplicates exist.
 */
private function ensureUniqueSlug(string $baseSlug, ?int $ignoreId = null): string
{
    $slug = $baseSlug;
    $i = 2;

    while (true) {
        $builder = $this->roleModel->where('slug', $slug);
        if ($ignoreId !== null) {
            $builder->where('id !=', $ignoreId);
        }

        $exists = $builder->countAllResults() > 0;

        if (!$exists) {
            break;
        }

        $slug = $baseSlug . '_' . $i;
        $i++;

        if ($i > 100000) { // safety stop
            break;
        }
    }

    return $slug;
}



    public function editRole($id)
    {
        if (!$role = $this->roleModel->find($id)) {
            return redirect()->to('rolepermission/roles')->with('error', 'Role not found.');
        }

        return view('rolepermission/edit_role', ['role' => $role]);
    }

public function updateRole($id)
{
    $role = $this->roleModel->find($id);
    if (!$role) {
        return redirect()->to('rolepermission/roles')->with('error', 'Role not found.');
    }

    $validation = \Config\Services::validation();
    // CI4 is_unique format: is_unique[table.field,ignore_field,ignore_value]
    $validation->setRules([
        'name'            => "required|min_length[3]|max_length[255]|is_unique[roles.name,id,{$id}]",
        'slug'            => "permit_empty|max_length[64]|is_unique[roles.slug,id,{$id}]",
        'description'     => 'permit_empty|max_length[500]',
        'dashboard_route' => [
            'rules'  => 'required|min_length[1]|max_length[255]|regex_match[/^[a-z0-9_\\/\\-]+$/]',
            'errors' => [
                'regex_match' => 'Route may contain lowercase letters, numbers, /, -, and _.'
            ]
        ],
        'priority'        => 'required|integer|greater_than_equal_to[0]|less_than_equal_to[100000]',
        'is_active'       => 'required|in_list[0,1]',
    ]);

    if (!$validation->withRequest($this->request)->run()) {
        return redirect()->back()->withInput()->with('errors', $validation->getErrors());
    }

    // Normalize inputs (lowercase storage)
    $rawName   = trim((string) $this->request->getPost('name'));
    $rawSlug   = trim((string) $this->request->getPost('slug'));
    $route     = trim((string) $this->request->getPost('dashboard_route'));
    $desc      = trim((string) $this->request->getPost('description'));
    $priority  = (int) $this->request->getPost('priority');
    $isActive  = (string) $this->request->getPost('is_active') === '1' ? 1 : 0;

    $name            = strtolower($rawName);
    $dashboard_route = strtolower($route);
    $description     = strtolower($desc);

    // slug: if blank, derive from name; else normalize
    $slug = $rawSlug !== '' ? $rawSlug : $name;
    $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '_', $slug));
    $slug = trim($slug, '_');

    // Ensure unique slug if changed or blank turned into new value
    if ($slug !== $role['slug']) {
        $slug = $this->ensureUniqueSlug($slug, (int) $id);
    }

    $data = [
        'name'            => $name,
        'slug'            => $slug,
        'description'     => $description,
        'dashboard_route' => $dashboard_route,
        'priority'        => $priority,
        'is_active'       => $isActive,
        'updated_at'      => utc_now(),
    ];

    if ($this->roleModel->update($id, $data)) {
        return redirect()->to('rolepermission/roles')->with('success', 'Role updated successfully!');
    }

    return redirect()->back()->withInput()->with('error', 'Failed to update role.');
}





    public function deleteRole($id)
    {
        // Check if role exists
        $role = $this->roleModel->find($id);
        if (!$role) {
            return redirect()->to(site_url('rolepermission/roles'))
                ->with('error', 'Role not found.');
        }

        // Try deleting
        if ($this->roleModel->delete($id)) {
            return redirect()->to(site_url('rolepermission/roles'))
                ->with('success', 'Role deleted successfully.');
        } else {
            return redirect()->to(site_url('rolepermission/roles'))
                ->with('error', 'Failed to delete role.');
        }
    }

    // ADD: show form + handle submit
    public function addPermission()
    {
        if (strtolower($this->request->getMethod()) === 'post') {
            $name = trim((string) $this->request->getPost('name'));
            $desc = trim((string) $this->request->getPost('description'));

            // Basic guard
            if ($name === '') {
                return redirect()->back()
                    ->with('error', 'Permission name is required.')
                    ->withInput();
            }

            // Check existence (collation is case-insensitive: utf8mb4_unicode_ci)
            $existing = $this->permissionModel->where('name', $name)->first();

            if ($existing) {
                // Idempotent: do nothing if already exists
                return redirect()->to(site_url('rolepermission/list_permissions'))
                    ->with('success', 'Permission already exists. No changes made.');
            }

            // Create new
            $data = [
                'name'        => $name,
                'description' => $desc,
            ];

            if (!$this->permissionModel->save($data)) {
                return redirect()->back()
                    ->with('error', implode(' ', $this->permissionModel->errors()))
                    ->withInput();
            }

            return redirect()->to(site_url('rolepermission/list_permissions'))
                ->with('success', 'Permission added successfully.');
        }

        // GET -> form
        return view('rolepermission/add_permission');
    }


    // EDIT: show form + handle submit
    public function editPermission($id = null)
    {
        $permission = $this->permissionModel->find($id);
        if (!$permission) {
            return redirect()->to(site_url('roles'))->with('error', 'Permission not found.');
        }

        if (strtolower($this->request->getMethod()) === 'post') {
            $data = [
                'id'          => $id, // needed for is_unique ignore
                'name'        => trim($this->request->getPost('name')),
                'description' => trim($this->request->getPost('description')),
            ];

            if (!$this->permissionModel->save($data)) {
                return redirect()->back()
                    ->with('error', implode(' ', $this->permissionModel->errors()))
                    ->withInput();
            }

            return redirect()->to(site_url('rolepermission/list_permissions'))
                ->with('success', 'Permission updated successfully.');
        }

        return view('rolepermission/edit_permission', ['permission' => $permission]);
    }

    // Optional delete
    public function deletePermission($id = null)
    {
        if (!$this->permissionModel->find($id)) {
            return redirect()->to(site_url('rolepermission/list_permissions'))->with('error', 'Permission not found.');
        }

        $this->permissionModel->delete($id);
        return redirect()->to(site_url('rolepermission/list_permissions'))->with('success', 'Permission deleted.');
    }

    public function listPermissions()
    {
        helper('url');
        return view('rolepermission/permissionList', [
            'permissionsEndpoint' => site_url('api/rolepermission/permissions'),
        ]);
    }

    public function permissionsData()
    {
        $rows = $this->permissionModel
            ->select('id, name, description, created_at, updated_at')
            ->orderBy('id', 'ASC')
            ->findAll();

        $normalized = array_map(static function ($row) {
            if (!is_array($row)) {
                return [];
            }
            return [
                'id'          => (int) ($row['id'] ?? 0),
                'name'        => (string) ($row['name'] ?? ''),
                'description' => $row['description'] ?? null,
                'created_at'  => $row['created_at'] ?? null,
                'updated_at'  => $row['updated_at'] ?? null,
            ];
        }, $rows ?? []);

        return $this->response->setJSON(['permissions' => $normalized]);
    }
}
