<?php

namespace App\Filters;

use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;

class AuthFilter implements FilterInterface
{
    protected $db;
    private const LOGIN_TTL = 43200; // 12 hours in seconds

    /**
     * Optional: map specific paths to role IDs and/or permission names.
     * - If a path is not listed here, the menu check ALLOWS by default.
     * - You can use either 'roles' (role IDs) or 'permission' (permission name) or both.
     * - 'crud' defaults to 'read' if omitted.
     *
     * Fill these to match your system or leave empty to rely only on route args.
     */
    protected array $menuRules = [
        // 'nav-builder' => ['permission' => 'nav_builder_access', 'crud' => 'read'],
        // 'admin/enrollment/new-students' => ['permission' => 'view_new_students', 'crud' => 'read'],
        // 'administrator/contact_information' => ['permission' => 'view_contact_info', 'crud' => 'read'],
        // You can also allow specific role IDs directly:
        // 'some/admin/page' => ['roles' => [1, 2]], // role_id 1 or 2
    ];

    public function __construct()
    {
        $this->db = \Config\Database::connect();
    }

    public function before(RequestInterface $request, $arguments = null)
    {
        $session   = session();
        $userRoles = $session->get('roles');
        $userId    = $session->get('user_id');
        $loginTime = (int) $session->get('login_time');

        // Must be logged in
        if (empty($userRoles) || empty($userId)) {
            $target = ltrim($request->getUri()->getPath(), '/');
            $query = $request->getUri()->getQuery();
            if ($query !== '') {
                $target .= '?' . $query;
            }

            return redirect()->to('/login?redirect_to=' . rawurlencode('/' . ltrim($target, '/')));
        }

        // Enforce 12-hour session lifetime
        if ($loginTime <= 0 || (time() - $loginTime) >= self::LOGIN_TTL) {
            return $this->handleExpiredSession($request);
        }

        // Load role IDs for this user
        $roleIdRows = $this->db->table('user_roles')
            ->select('role_id')
            ->where('user_id', $userId)
            ->get()->getResultArray();

        $roleIds = array_map('intval', array_column($roleIdRows, 'role_id'));
        if (empty($roleIds)) {
            return $this->deny($request, "You don't have permission to use this feature.");
        }

        [$requirements, $crudAction] = $this->parseRequirements($arguments);

        if (!empty($requirements)) {
            if ($this->matchesAnyRequirement($requirements, $crudAction, $roleIds, (array) $userRoles)) {
                return;
            }

            return $this->deny($request, "You don't have permission to use this feature.");
        }

        if ($this->isAllowedByMenu($request, $roleIds)) {
            return; // ✅ allowed
        }

        return $this->deny($request, "You don't have permission to use this feature.");
    }

    private function handleExpiredSession(RequestInterface $request)
    {
        session()->destroy();

        if ($request->isAJAX() || $request->getHeaderLine('Accept') === 'application/json') {
            return service('response')
                ->setStatusCode(401)
                ->setJSON([
                    'status' => 'expired',
                    'message' => 'Your session has expired. Please log in again.',
                    'redirect' => '/login',
                ]);
        }

        $target = ltrim($request->getUri()->getPath(), '/');
        $query = $request->getUri()->getQuery();
        if ($query !== '') {
            $target .= '?' . $query;
        }

        return redirect()->to('/login?redirect_to=' . rawurlencode('/' . ltrim($target, '/')))
            ->with('error', 'Your session has expired. Please log in again.');
    }

    private function parseRequirements(?array $arguments): array
    {
        if (empty($arguments)) {
            return [[], 'read'];
        }

        $tokens = array_values(array_filter(array_map(static fn($value) => trim((string) $value), $arguments), static fn($value) => $value !== ''));
        if (empty($tokens)) {
            return [[], 'read'];
        }

        $crudAction = 'read';
        $last = strtolower((string) end($tokens));
        if (in_array($last, ['create', 'read', 'update', 'delete'], true)) {
            $crudAction = array_pop($tokens);
        }

        if (count($tokens) === 1 && in_array(strtolower($tokens[0]), ['create', 'read', 'update', 'delete'], true)) {
            return [[], strtolower($tokens[0])];
        }

        return [$tokens, strtolower((string) $crudAction)];
    }

    private function matchesAnyRequirement(array $requirements, string $crudAction, array $roleIds, array $sessionRoles): bool
    {
        $normalizedRoles = array_map(static fn($role) => strtolower(trim((string) $role)), $sessionRoles);

        foreach ($requirements as $requirement) {
            $alternatives = array_values(array_filter(array_map(static fn($value) => trim((string) $value), explode('|', (string) $requirement))));
            if (empty($alternatives)) {
                continue;
            }

            foreach ($alternatives as $alternative) {
                $candidate = strtolower($alternative);
                if (in_array($candidate, $normalizedRoles, true)) {
                    return true;
                }

                if ($this->userHasNamedPermission($roleIds, $alternative, $crudAction)) {
                    return true;
                }
            }
        }

        return false;
    }

    private function userHasNamedPermission(array $roleIds, string $permissionName, string $crudAction): bool
    {
        if (empty($roleIds)) {
            return false;
        }

        $rows = $this->db->table('role_permissions rp')
            ->join('permissions p', 'p.id = rp.permission_id')
            ->select('rp.*')
            ->whereIn('rp.role_id', $roleIds)
            ->where('LOWER(p.name)', strtolower($permissionName))
            ->get()
            ->getResultArray();

        foreach ($rows as $row) {
            if ($this->hasPermission($row, $crudAction)) {
                return true;
            }
        }

        return false;
    }

    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
    {
        // No-op
    }

    /**
     * JSON for AJAX, redirect for normal requests.
     * Use RequestInterface here (your version used IncomingRequest which was too specific).
     */
    public function deny(RequestInterface $request, string $message)
    {
        if ($request->isAJAX() || $request->getHeaderLine('Accept') === 'application/json') {
            return service('response')
                ->setStatusCode(403)
                ->setJSON(['status' => 'error', 'message' => $message]);
        }

        session()->setFlashdata('error', $message);
        return redirect()->to('/access_denied');
    }

    /**
     * Checks roles against CRUD flags on a role_permissions row.
     */
    private function hasPermission(array $rolePermission, string $crudAction): bool
    {
        switch ($crudAction) {
            case 'create':
                return !empty($rolePermission['can_create']);
            case 'read':
                return !empty($rolePermission['can_read']);
            case 'update':
                return !empty($rolePermission['can_update']);
            case 'delete':
                return !empty($rolePermission['can_delete']);
            default:
                return false;
        }
    }

    /**
     * ✅ Missing method added:
     * Allow/deny based on $menuRules and the user's role IDs / permissions.
     * Defaults to ALLOW if the path has no rule.
     */
    private function isAllowedByMenu(RequestInterface $request, array $roleIds): bool
    {
        // ❌ was: $request->uri->getPath()
        $path = ltrim(preg_replace('#/+#', '/', $request->getUri()->getPath()), '/');

        $rule = $this->menuRules[$path] ?? null;

        if ($rule === null && str_starts_with($path, 'admin/')) {
            $rule = $this->menuRules[substr($path, 6)] ?? null;
        }

        if ($rule === null) {
            return true; // or false if you prefer default-deny
        }

        if (!empty($rule['roles']) && is_array($rule['roles'])) {
            foreach ($roleIds as $rid) {
                if (in_array((int)$rid, $rule['roles'], true)) return true;
            }
            // fall through to permission check (if present)
        }

        if (!empty($rule['permission'])) {
            $crud = $rule['crud'] ?? 'read';
            $rpRows = $this->db->table('role_permissions rp')
                ->join('permissions p', 'p.id = rp.permission_id')
                ->select('rp.*')
                ->whereIn('rp.role_id', $roleIds)
                ->where('p.name', $rule['permission'])
                ->get()->getResultArray();

            foreach ($rpRows as $rp) {
                if ($this->hasPermission($rp, $crud)) return true;
            }
            return false;
        }

        return true;
    }
}
