<?php

/**
 * ============================================================
 *
 * @project   biiproject
 *
 * @author    Andika Debi Putra
 *
 * @email     andikadebiputra@gmail.com
 *
 * @website   https://biiproject.com
 *
 * @copyright Copyright (c) 2026 Andika Debi Putra
 * @license   Proprietary - All Rights Reserved
 *
 * @version   1.0.0
 *
 * @created   2026-05-01
 * ============================================================
 *
 * Unauthorized copying, modification, distribution, or use
 * of this file is strictly prohibited without prior written
 * permission from the author.
 * ============================================================
 */

namespace App\Http\Controllers\AccessControl;

use App\Models\Permission;
use App\Models\Role;
use App\Support\DataTable;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Rule;

class RoleManagementController extends Controller
{
    public function __construct()
    {
        // Middleware handled in web.php
    }

    public function index(Request $request)
    {
        if (DataTable::isDataTableRequest($request)) {
            return $this->dataTable($request);
        }

        $permissions = Permission::where('is_active', 1)
            ->whereNull('deleted_at')
            ->orderBy('name')
            ->get();

        // Categorize permissions for UI Matrix
        $groupedPermissions = $this->groupPermissions($permissions);

        return view('pages.access_control.roles', [
            'permissions' => $permissions,
            'groupedPermissions' => $groupedPermissions,
        ]);
    }

    /**
     * Group permissions into a hierarchical tree: category → menu → tabs.
     *
     * Each entry in $groups[$category][$baseName] has:
     *   'view'     => Permission|null   (menu-level view)
     *   'manage'   => Permission|null   (menu-level manage)
     *   'tabs'     => [
     *       $tabSlug => ['view' => Permission|null, 'manage' => Permission|null]
     *   ]
     */
    protected function groupPermissions($permissions)
    {
        $groups = [];

        foreach ($permissions as $permission) {
            $name = $permission->name;

            // Determine category
            $category = match (true) {
                str_contains($name, 'ai ')              => 'AI Intelligence',
                str_contains($name, 'dashboard')        => 'Dashboard',
                str_contains($name, 'user directory')   => 'User Directory',
                str_contains($name, 'access rights')    => 'Access Rights',
                str_contains($name, 'health and logs')  => 'Health & Logs',
                str_contains($name, 'action history')   => 'Action History',
                str_contains($name, 'global settings')  => 'Global Settings',
                str_contains($name, 'mobile settings')  => 'Mobile Settings',
                str_contains($name, 'active sessions')  => 'Active Sessions',
                str_contains($name, 'notification center') => 'Notification Center',
                str_contains($name, 'impersonate')      => 'Impersonation',
                str_contains($name, 'backup') || str_contains($name, 'maintenance') => 'System Config',
                default                                 => 'Other',
            };

            // Strip action prefix
            $type     = 'other';
            $resource = $name;
            if (str_starts_with($name, 'manage ')) {
                $type     = 'manage';
                $resource = substr($name, 7);
            } elseif (str_starts_with($name, 'view ')) {
                $type     = 'view';
                $resource = substr($name, 5);
            }

            // Detect scoped (tab-level) permission: "resource:tab-slug"
            if (str_contains($resource, ':')) {
                [$menu, $tab] = explode(':', $resource, 2);
                $menu = trim($menu);
                $tab  = trim($tab);

                if (! isset($groups[$category][$menu])) {
                    $groups[$category][$menu] = ['view' => null, 'manage' => null, 'tabs' => []];
                }
                if (! isset($groups[$category][$menu]['tabs'][$tab])) {
                    $groups[$category][$menu]['tabs'][$tab] = ['view' => null, 'manage' => null];
                }
                $groups[$category][$menu]['tabs'][$tab][$type] = $permission;
            } else {
                $menu = trim($resource);
                if (! isset($groups[$category][$menu])) {
                    $groups[$category][$menu] = ['view' => null, 'manage' => null, 'tabs' => []];
                }
                $groups[$category][$menu][$type] = $permission;
            }
        }

        // Sort categories
        $priority = ['Dashboard', 'User Directory', 'Access Rights', 'AI Intelligence', 'Health & Logs', 'Action History', 'Global Settings', 'Mobile Settings', 'Notification Center', 'Active Sessions', 'Impersonation', 'System Config'];
        uksort($groups, function ($a, $b) use ($priority) {
            $posA = array_search($a, $priority);
            $posB = array_search($b, $priority);
            if ($posA === false && $posB === false) {
                return strcmp($a, $b);
            }
            return ($posA === false) ? 1 : (($posB === false) ? -1 : $posA - $posB);
        });

        return $groups;
    }

    protected function dataTable(Request $request)
    {
        try {
            $authUser = $request->user();
            $canManage = $authUser->can('manage access rights');
            $query = Role::query()->withTrashed()->with(['permissions:id,name', 'creator:id,email', 'updater:id,email']);
            $globalSearch = DataTable::globalSearch($request);

            $trashed = $request->input('trashed');
            if ($trashed === 'archived') {
                $query->onlyTrashed();
            } elseif ($trashed === 'active') {
                $query->withoutTrashed();
            }

            if ($canManage) {
                $status = DataTable::columnSearch($request, 1);
                $name = DataTable::columnSearch($request, 2);
                $guard = DataTable::columnSearch($request, 3);
                $permission = DataTable::columnSearch($request, 4);
            } else {
                $status = null;
                $name = DataTable::columnSearch($request, 0);
                $guard = DataTable::columnSearch($request, 1);
                $permission = DataTable::columnSearch($request, 2);
            }

            if ($status) {
                $query->where('is_active', $status === 'active');
            }

            if ($name) {
                $query->where('name', 'like', "%{$name}%");
            }

            if ($guard) {
                $query->where('guard_name', 'like', "%{$guard}%");
            }

            if ($permission) {
                $query->whereHas('permissions', function ($permissionQuery) use ($permission) {
                    $permissionQuery->where('name', 'like', "%{$permission}%");
                });
            }

            // Audit columns (fixed indices from the end or just using canManage logic)
            if ($canManage) {
                $createdAt = DataTable::columnSearch($request, 5);
                $createdBy = DataTable::columnSearch($request, 6);
                $updatedAt = DataTable::columnSearch($request, 7);
                $updatedBy = DataTable::columnSearch($request, 8);
            } else {
                $createdAt = DataTable::columnSearch($request, 3);
                $createdBy = DataTable::columnSearch($request, 4);
                $updatedAt = DataTable::columnSearch($request, 5);
                $updatedBy = DataTable::columnSearch($request, 6);
            }

            if ($createdAt) {
                $query->whereDate('created_at', $createdAt);
            }

            if ($createdBy) {
                $query->whereHas('creator', function ($creatorQuery) use ($createdBy) {
                    $creatorQuery->where('email', 'like', "%{$createdBy}%");
                });
            }

            if ($updatedAt) {
                $query->whereDate('updated_at', $updatedAt);
            }

            if ($updatedBy) {
                $query->whereHas('updater', function ($updaterQuery) use ($updatedBy) {
                    $updaterQuery->where('email', 'like', "%{$updatedBy}%");
                });
            }

            if ($globalSearch) {
                $query->where(function ($searchQuery) use ($globalSearch) {
                    $searchQuery
                        ->where('name', 'like', "%{$globalSearch}%")
                        ->orWhere('guard_name', 'like', "%{$globalSearch}%")
                        ->orWhereHas('permissions', function ($permissionQuery) use ($globalSearch) {
                            $permissionQuery->where('name', 'like', "%{$globalSearch}%");
                        })
                        ->orWhereHas('creator', function ($creatorQuery) use ($globalSearch) {
                            $creatorQuery->where('email', 'like', "%{$globalSearch}%");
                        })
                        ->orWhereHas('updater', function ($updaterQuery) use ($globalSearch) {
                            $updaterQuery->where('email', 'like', "%{$globalSearch}%");
                        });
                });
            }

            // Perform filtered count WITHOUT eager loading or ordering
            $countQuery = clone $query;
            $countQuery->setEagerLoads([]);
            $countQuery->orders = null;
            $recordsFiltered = $countQuery->count();

            $recordsTotal = Role::withTrashed()->count(); // Total across all states without eager loads

            [$orderIndex, $orderDirection] = DataTable::order($request, $canManage ? 5 : 3, 'desc');

            $sortColumn = match (true) {
                $canManage && $orderIndex === 1 => 'is_active',
                ($canManage && $orderIndex === 2) || (! $canManage && $orderIndex === 0) => 'name',
                ($canManage && $orderIndex === 3) || (! $canManage && $orderIndex === 1) => 'guard_name',
                ($canManage && $orderIndex === 5) || (! $canManage && $orderIndex === 3) => 'created_at',
                ($canManage && $orderIndex === 7) || (! $canManage && $orderIndex === 5) => 'updated_at',
                default => 'created_at',
            };

            $roles = $query
                ->orderBy($sortColumn, $orderDirection)
                ->skip(DataTable::start($request))
                ->take(DataTable::length($request))
                ->get();

            $rows = $roles->map(function (Role $role) use ($canManage) {
                $permissionNames = $role->permissions->pluck('name')->values();
                $permissionBadges = $permissionNames->isNotEmpty()
                    ? $permissionNames->map(fn ($permission) => '<span class="badge badge-slate-pill">'.e($permission).'</span>')->implode(' ')
                    : '-';

                $row = [];

                if ($canManage) {
                    $row[] = sprintf(
                        '<input type="checkbox" class="form-check-input role-checkbox" value="%d">',
                        $role->id
                    );
                    $row[] = sprintf(
                        '<div class="form-check form-switch d-flex align-items-center gap-2"><input class="form-check-input role-toggle" type="checkbox" %s data-id="%d" data-name="%s"><span class="status-label %s">%s</span></div>',
                        $role->is_active ? 'checked' : '',
                        $role->id,
                        e($role->name),
                        $role->is_active ? 'text-success' : 'text-danger',
                        $role->is_active ? 'Active' : 'Inactive'
                    );
                }

                $row[] = e($role->name);
                $row[] = e($role->guard_name);
                $row[] = '<div class="permission-clamp" title="'.e($permissionNames->implode(', ')).'">'.$permissionBadges.'</div>';
                $row[] = e(format_datetime($role->created_at));
                $row[] = e($role->creator->email ?? 'System');
                $row[] = e(format_datetime($role->updated_at));
                $row[] = e($role->updater->email ?? '-');

                if ($canManage) {
                    if ($role->trashed()) {
                        $row[] = '<div class="text-end d-inline-flex gap-2 justify-content-end w-100">'
                            .'<button class="btn btn-link text-success btn-restore" data-id="'.$role->id.'" data-name="'.e($role->name).'"><i class="bi bi-arrow-counterclockwise"></i> '.e(__('Restore')).'</button>'
                            .'<button class="btn btn-link text-danger btn-force-delete" data-id="'.$role->id.'" data-name="'.e($role->name).'"><i class="bi bi-x-circle"></i> '.e(__('Terminate')).'</button>'
                            .'</div>';
                    } else {
                        $row[] = '<div class="text-end d-inline-flex gap-2 justify-content-end w-100">'
                            .'<button class="btn btn-link btn-edit" data-id="'.$role->id.'" data-name="'.e($role->name).'" data-guard="'.e($role->guard_name).'" data-permissions="'.e(json_encode($role->permissions->pluck('id')->values()->all())).'" data-created="'.e(format_datetime($role->created_at)).'" data-created-by="'.e($role->creator->email ?? 'System').'" data-updated-at="'.e(format_datetime($role->updated_at)).'" data-updated-by="'.e($role->updater->email ?? '-').'" data-bs-toggle="modal" data-bs-target="#editRoleModal"><i class="bi bi-pencil"></i></button>'
                            .'<button class="btn btn-link text-danger btn-delete" type="button" data-id="'.$role->id.'" data-name="'.e($role->name).'"><i class="bi bi-trash"></i></button>'
                            .'</div>';
                    }
                }

                return $row;
            })->all();

            return DataTable::response($request, $recordsTotal, $recordsFiltered, $rows);
        } catch (\Exception $e) {
            Log::error('DataTable Error [RoleManagement]: '.$e->getMessage());

            return DataTable::response($request, 0, 0, []);
        }
    }

    public function store(Request $request)
    {
        $request->validate([
            'name' => [
                'required',
                'string',
                'min:3',
                'max:50',
                'regex:/^[a-zA-Z0-9_\-]+$/',
                Rule::unique('roles', 'name'),
            ],
            'guard_name' => [
                'required',
                'in:web,api',
            ],
            'permissions' => [
                'required',
                'array',
                'min:1',
            ],
            'permissions.*' => [
                Rule::exists('permissions', 'id'),
            ],
        ]);

        try {
            $role = Role::create([
                'name' => $request->name,
                'guard_name' => $request->guard_name,
                'created_by' => Auth::id(),
            ]);

            $permissionNames = Permission::whereIn('id', $request->permissions)
                ->pluck('name')
                ->toArray();

            $role->syncPermissions($permissionNames);

            if ($request->expectsJson()) {
                return response()->json([
                    'success' => true,
                    'message' => __('Role has been successfully created and permissions assigned.'),
                ]);
            }

            return back()->with('success', __('Role has been successfully created and permissions assigned.'));
        } catch (\Exception $e) {
            if ($request->expectsJson()) {
                return response()->json([
                    'success' => false,
                    'message' => __('Failed to create role: ').$e->getMessage(),
                ], 500);
            }

            return back()->with('error', __('Failed to create role: ').$e->getMessage());
        }
    }

    public function update(Request $request, $id)
    {
        $request->validate([
            'name' => [
                'required',
                'string',
                'min:3',
                'max:50',
                'regex:/^[a-zA-Z0-9_\-]+$/',
                Rule::unique('roles', 'name')->ignore($id),
            ],
            'guard_name' => [
                'required',
                'in:web,api',
            ],
            'permissions' => [
                'required',
                'array',
                'min:1',
            ],
            'permissions.*' => [
                Rule::exists('permissions', 'id'),
            ],
        ]);

        try {
            $role = Role::findOrFail($id);

            $role->update([
                'name' => $request->name,
                'guard_name' => $request->guard_name,
                'updated_by' => Auth::id(),
            ]);

            $permissionNames = Permission::whereIn('id', $request->permissions)
                ->pluck('name')
                ->toArray();

            $role->syncPermissions($permissionNames);

            if ($request->expectsJson()) {
                return response()->json([
                    'success' => true,
                    'message' => __('Role successfully updated.'),
                ]);
            }

            return back()->with('success', __('Role successfully updated.'));
        } catch (\Exception $e) {
            if ($request->expectsJson()) {
                return response()->json([
                    'success' => false,
                    'message' => __('Failed to update role: ').$e->getMessage(),
                ], 500);
            }

            return back()->with('error', __('Failed to update role: ').$e->getMessage());
        }
    }

    public function toggleStatus(Request $request)
    {
        $request->validate([
            'id' => 'required|exists:roles,id',
            'status' => 'required|in:activate,deactivate',
        ]);

        $role = Role::findOrFail($request->id);

        $role->update([
            'is_active' => $request->status === 'activate' ? 1 : 0,
            'updated_by' => Auth::id(),
        ]);

        return response()->json([
            'success' => true,
            'message' => __('Role status updated successfully.'),
        ]);
    }

    public function destroy($id)
    {
        $role = Role::findOrFail($id);

        // Check if role is still used by users
        if ($role->users()->exists()) {
            return response()->json([
                'success' => false,
                'message' => __('Cannot archive. This role is still assigned to some users.'),
            ], 422);
        }

        $role->delete();

        return response()->json([
            'success' => true,
            'message' => __('Role has been archived.'),
        ]);
    }

    /**
     * RESTORE — Restore role (Soft Delete)
     */
    public function restore($id)
    {
        $role = Role::withTrashed()->findOrFail($id);
        $role->restore();

        return response()->json([
            'success' => true,
            'message' => __('Role has been restored.'),
        ]);
    }

    /**
     * FORCE DELETE — Permanent removal
     */
    public function forceDelete($id)
    {
        $role = Role::withTrashed()->findOrFail($id);

        // Final usage check before permanent deletion
        if ($role->users()->exists()) {
            return response()->json([
                'success' => false,
                'message' => __('Cannot delete permanently. This role is still assigned to some users.'),
            ], 422);
        }

        $role->forceDelete();

        return response()->json([
            'success' => true,
            'message' => __('Role has been permanently removed.'),
        ]);
    }

    public function bulkToggleStatus(Request $request)
    {
        $request->validate([
            'ids' => 'required|array',
            'ids.*' => 'exists:roles,id',
            'status' => 'required|in:activate,deactivate',
        ]);

        Role::whereIn('id', $request->ids)->update([
            'is_active' => $request->status === 'activate' ? 1 : 0,
            'updated_by' => Auth::id(),
        ]);

        return response()->json([
            'success' => true,
            'message' => __('Selected roles status updated successfully.'),
        ]);
    }

    public function bulkDelete(Request $request)
    {
        $request->validate([
            'ids' => 'required|array',
            'ids.*' => 'exists:roles,id',
        ]);

        $roles = Role::whereIn('id', $request->ids)->get();
        $archivedCount = 0;
        $failedIds = [];

        foreach ($roles as $role) {
            if (! $role->users()->exists()) {
                $role->delete();
                $archivedCount++;
            } else {
                $failedIds[] = $role->name;
            }
        }

        $message = __(':count roles archived.', ['count' => $archivedCount]);
        if (! empty($failedIds)) {
            $message .= ' '.__('Could not archive :names because they are still assigned to users.', ['names' => implode(', ', $failedIds)]);
        }

        return response()->json([
            'success' => true,
            'message' => $message,
        ]);
    }

    public function bulkRestore(Request $request)
    {
        $request->validate([
            'ids' => 'required|array',
            'ids.*' => 'exists:roles,id',
        ]);

        Role::withTrashed()->whereIn('id', $request->ids)->restore();

        return response()->json([
            'success' => true,
            'message' => __('Selected roles restored successfully.'),
        ]);
    }

    public function bulkForceDelete(Request $request)
    {
        $request->validate([
            'ids' => 'required|array',
            'ids.*' => 'exists:roles,id',
        ]);

        $roles = Role::withTrashed()->whereIn('id', $request->ids)->get();
        $deletedCount = 0;
        $failedIds = [];

        foreach ($roles as $role) {
            if (! $role->users()->exists()) {
                $role->forceDelete();
                $deletedCount++;
            } else {
                $failedIds[] = $role->name;
            }
        }

        $message = __(':count roles permanently removed.', ['count' => $deletedCount]);
        if (! empty($failedIds)) {
            $message .= ' '.__('Could not remove :names because they are still assigned to users.', ['names' => implode(', ', $failedIds)]);
        }

        return response()->json([
            'success' => true,
            'message' => $message,
        ]);
    }
}
