<?php

namespace App\Controllers;

use App\Models\LoginActivityModel;
use App\Models\UserModel;
use App\Models\UserRoleModel;
use CodeIgniter\Events\Events;
use App\Models\IpAttemptModel;
use App\Models\PasswordResetModel;
use CodeIgniter\I18n\Time;
use App\Models\RoleModel;
use App\Models\ConfigurationModel;
use App\Models\PreferencesModel;


require_once APPPATH . 'Helpers/pbkdf2_helper.php';
require_once APPPATH . 'Helpers/jwt_helper.php';


class AuthController extends BaseController
{
    protected $configModel;
    protected $userModel;
    protected $roleModel;
    protected $schoolYear;
    protected $semester;
    protected $ipAttemptModel;
    protected $loginActivityModel;
    protected $preferencesModel;

    public function __construct()
    {   
        $this->userModel = new UserModel();
        $this->roleModel = new RoleModel();
        $this->configModel = new ConfigurationModel();
        $this->ipAttemptModel = new IpAttemptModel();
        $this->loginActivityModel = new LoginActivityModel();
        $this->preferencesModel = new PreferencesModel();
        $this->schoolYear = $this->configModel->getConfig('school_year');
        $this->semester = $this->configModel->getConfig('semester');
        
    }

    public function setModel($model)
    {
        $this->userModel = $model;
    }

    private function scheduleEvent($userId)
    {
        // Schedule the event to run after 2 minutes (120 seconds)
        Events::trigger('delete_unverified_user', $userId);
    }
   
    public function loginMask()
    {
        $redirectTo = $this->sanitizeRedirectTarget((string) ($this->request->getGet('redirect_to') ?? ''));

        if (session()->get('is_logged_in')) {
            if ($redirectTo !== null) {
                return redirect()->to($redirectTo);
            }

            $roles = session()->get('roles') ?? [];
            if (empty($roles) && session()->get('role')) {
                $roles = [session()->get('role')];
            }

            if (!empty($roles)) {
                return $this->redirectToDashboard($roles);
            }
        }

        return view('user/login', [
            'redirect_to' => $redirectTo ?? '',
        ]);
    }
    
    public function login()
    {
        log_message('info', 'Processing login form submission.');
        $redirectTo = $this->sanitizeRedirectTarget((string) ($this->request->getPost('redirect_to') ?? $this->request->getGet('redirect_to') ?? ''));

        $validator = \Config\Services::validation();
        $requestData = sanitize_request_value($this->request->getPost(['email', 'password']));
        $validator->setRules([
            'email' => 'required|valid_email|max_length[254]',
            'password' => 'required|max_length[255]',
        ]);

        if (! $validator->run($requestData)) {
            return redirect()
                ->back()
                ->with('error', 'Please enter a valid email and password.')
                ->withInput();
        }

        // Step 1: Get email, password, and IP from the request
        $email = strtolower((string) $requestData['email']);
        $password = (string) $requestData['password'];
        $ip = $this->request->getIPAddress();

        log_message('info', 'Login attempt from IP: ' . $ip . ' for email: ' . $email);

        // Step 2: Check if the IP is blocked (too many failed attempts)
        if ($this->isIpBlocked($ip)) {
            return redirect()
                ->back()
                ->with('error', 'Too many failed attempts from your IP. Please try again later.')
                ->withInput(); // ✅ preserve email input
        }

        // Step 3: Look up user by email
        $user = $this->getUserByEmail($email);

        if ($user) {
            // Step 4: Check if user is suspended
            if ($this->checkIfUserSuspended($user)) {
                return redirect()
                    ->back()
                    ->with('error', 'Account suspended. Please check your email to reset your password.')
                    ->withInput(); // ✅ preserve email input
            }

            // Step 5: Verify password
            if ($this->verifyPassword($password, $user['password'])) {
                // Step 6: Login successful — reset failed attempts and log
                $this->resetFailedAttempts($user['id']);
                $this->logLoginAttempt($user['id'], $user['email'], $ip, $this->request->getUserAgent());

                // ✅ Step 7: Call loginUser() to set session and redirect
                return $this->loginUser($user, $redirectTo);
            } else {
                // Step 8: Password mismatch — log failed attempt
                $this->handleFailedLogin($user['id'], $user['email'], $ip);
                return redirect()
                    ->back()
                    ->with('error', 'The email and password combination you entered is invalid. Please try again.')
                    ->withInput(); // ✅ preserve email input
            }
        } else {
            // Step 9: No user found — log IP-level attempt
            $this->logIpAttempt($ip);
            return redirect()
                ->back()
                ->with('error', 'The email and password combination you entered is invalid. Please try again.')
                ->withInput(); // ✅ preserve email input
        }
    }

    // JSON API: POST /api/login
    public function apiLogin()
    {
        $requestData = sanitize_request_value($this->request->getJSON(true) ?: [
            'email' => $this->request->getPost('email'),
            'password' => $this->request->getPost('password'),
        ]);

        $validation = \Config\Services::validation();
        $validation->setRules([
            'email' => 'required|valid_email|max_length[254]',
            'password' => 'required|max_length[255]',
        ]);

        if (! $validation->run($requestData)) {
            return $this->response->setStatusCode(422)->setJSON([
                'status' => false,
                'message' => 'Validation failed.',
                'errors' => $validation->getErrors(),
            ]);
        }

        $email = strtolower((string) ($requestData['email'] ?? ''));
        $password = (string) ($requestData['password'] ?? '');
        $ip = $this->request->getIPAddress();

        if ($this->isIpBlocked($ip)) {
            return $this->response->setStatusCode(429)->setJSON([
                'status' => false,
                'message' => 'Too many failed attempts from your IP. Please try again later.'
            ]);
        }

        $user = $this->getUserByEmail($email);
        if (!$user) {
            $this->logIpAttempt($ip);
            return $this->response->setStatusCode(401)->setJSON([
                'status' => false,
                'message' => 'Invalid email or password.'
            ]);
        }

        if ($this->checkIfUserSuspended($user)) {
            return $this->response->setStatusCode(403)->setJSON([
                'status' => false,
                'message' => 'Account suspended. Please reset your password.'
            ]);
        }

        if (!$this->verifyPassword($password, $user['password'])) {
            $this->handleFailedLogin($user['id'], $user['email'], $ip);
            return $this->response->setStatusCode(401)->setJSON([
                'status' => false,
                'message' => 'Invalid email or password.'
            ]);
        }

        // Success: reset attempts + log
        $this->resetFailedAttempts($user['id']);
        $this->logLoginAttempt($user['id'], $user['email'], $ip, $this->request->getUserAgent());

        // Fetch roles
        $roleNames = $this->getUserRoleNames((int) $user['id']);

        // Build roles map (object with keys per example)
        $rolesMap = [];
        foreach ($roleNames as $r) {
            $rolesMap[$r] = true;
        }

        // Build JWT token
        $now = time();
        $exp = $now + 60 * 60 * 24; // 24h default
        $payload = [
            'sub' => (int) $user['id'],
            'name' => trim(($user['firstname'] ?? '') . ' ' . ($user['lastname'] ?? '')),
            'roles' => $roleNames,
            'iat' => $now,
            'exp' => $exp,
        ];

        try {
            $secret = require_env('JWT_SECRET');
        } catch (\RuntimeException $e) {
            return $this->response->setStatusCode(500)->setJSON([
                'status' => false,
                'message' => 'JWT configuration is missing.',
            ]);
        }

        $token = jwt_encode($payload, $secret, 'HS256');

        return $this->response->setJSON([
            'status' => true,
            'token' => $token,
            'user' => [
                'id' => (int) $user['id'],
                'name' => $payload['name'],
                'roles' => (object) $rolesMap,
            ],
        ]);
    }

    /**
     * API Registration endpoint
     * POST /api/v1/register
     */
    public function apiRegister()
    {
        $requestData = sanitize_request_value($this->request->getJSON(true) ?: $this->request->getPost());

        // Basic validation
        $rules = [
            'firstname' => "required|regex_match[/^[\\p{L}\\s'\\-]+$/u]|min_length[2]|max_length[30]",
            'lastname' => "required|regex_match[/^[\\p{L}\\s'\\-]+$/u]|min_length[2]|max_length[30]",
            'email' => 'required|valid_email|max_length[254]|is_unique[users.email]',
            'password' => 'required|min_length[8]|max_length[255]',
            'cellphone' => 'required|min_length[10]|max_length[20]',
        ];

        $validation = \Config\Services::validation();
        $validation->setRules($rules);

        if (!$validation->run($requestData)) {
            return $this->response->setStatusCode(422)->setJSON([
                'status' => false,
                'message' => 'Validation failed',
                'errors' => $validation->getErrors(),
            ]);
        }

        // Check if email already exists
        $existingUser = $this->userModel->where('email', $requestData['email'])->first();
        if ($existingUser) {
            return $this->response->setStatusCode(422)->setJSON([
                'status' => false,
                'message' => 'Email already registered',
            ]);
        }

        // Hash password
        require_once APPPATH . 'Helpers/pbkdf2_helper.php';
        $hashedPassword = pbkdf2_hash($requestData['password']);

        // Prepare user data
        $userData = [
            'firstname' => $requestData['firstname'],
            'lastname' => $requestData['lastname'],
            'email' => $requestData['email'],
            'password' => $hashedPassword,
            'cellphone' => $requestData['cellphone'],
            'gender' => $requestData['gender'] ?? null,
            'address_street' => $requestData['address_street'] ?? null,
            'city' => $requestData['city'] ?? null,
            'state' => $requestData['state'] ?? null,
            'zip' => $requestData['zip'] ?? null,
            'school_year' => $this->configModel->getConfig('school_year'),
            'semester' => $this->configModel->getConfig('semester'),
            'status' => 'active',
            'is_verified' => 0, // Require email verification
        ];

        try {
            $userId = $this->userModel->insert($userData);
            if (!$userId) {
                return $this->response->setStatusCode(500)->setJSON([
                    'status' => false,
                    'message' => 'Failed to create user account',
                    'errors' => $this->userModel->errors(),
                ]);
            }

            // Assign parent role if specified
            $roleId = null;
            if (isset($requestData['role']) && $requestData['role'] === 'parent') {
                $role = $this->roleModel->where('name', 'parent')->first();
                if ($role) {
                    $roleId = $role['id'];
                    $userRoleModel = new \App\Models\UserRoleModel();
                    $userRoleModel->insert([
                        'user_id' => $userId,
                        'role_id' => $roleId,
                    ]);
                }
            }

            // Generate JWT token
            $now = time();
            $exp = $now + 60 * 60 * 24; // 24h
            $payload = [
                'sub' => (int) $userId,
                'name' => trim($userData['firstname'] . ' ' . $userData['lastname']),
                'roles' => $roleId ? ['parent'] : [],
                'iat' => $now,
                'exp' => $exp,
            ];

            $secret = require_env('JWT_SECRET');
            $token = jwt_encode($payload, $secret, 'HS256');

            return $this->response->setStatusCode(201)->setJSON([
                'status' => true,
                'message' => 'Registration successful. Please verify your email.',
                'token' => $token,
                'user' => [
                    'id' => (int) $userId,
                    'name' => $payload['name'],
                    'email' => $userData['email'],
                    'roles' => $payload['roles'],
                ],
            ]);
        } catch (\Exception $e) {
            log_message('error', 'Registration error: ' . $e->getMessage());
            return $this->response->setStatusCode(500)->setJSON([
                'status' => false,
                'message' => 'Registration failed. Please try again.',
            ]);
        }
    }

    private function verifyPassword($password, $storedHash)
    {
        if (!function_exists('pbkdf2_verify')) {
            die('pbkdf2_verify() is NOT loaded');
        }

        return pbkdf2_verify($password, $storedHash);
    }

    private function handleFailedLogin($userId, $email, $ip)
    {
        // Get user data
        $user = $this->userModel->find($userId);
        $failedAttempts = $user['failed_attempts'] + 1;
        $data = ['failed_attempts' => $failedAttempts, 'last_failed_at' => utc_now()];

        // Check if the failed attempts have reached 3
        if ($failedAttempts >= 3) {
            // Suspend the account
            $data['is_suspended'] = true;
            
            // Set a warning message in session to inform the user
            session()->setFlashdata('warning_message', 'Your account has been suspended due to multiple failed login attempts. A reset password email has been sent to your registered email address.');
            
            // Send the password reset email
            $this->sendPasswordResetEmail($email);
        }

        // Update the user record
        $this->userModel->update($userId, $data);

        // Log the IP attempt (track failed login attempts)
        $this->logIpAttempt($ip);
    }

    private function isIpBlocked($ip)
    {
        $attempt = $this->ipAttemptModel->where('ip_address', $ip)->first();
        return $attempt && $attempt['blocked_until'] > utc_now();
    }

    private function logIpAttempt($ip)
    {
        $now = utc_now();
        $attempt = $this->ipAttemptModel->where('ip_address', $ip)->first();

        if ($attempt) {
            $attempts = $attempt['attempts'] + 1;
            $blockedUntil = $attempts >= 10 ? date('Y-m-d H:i:s', strtotime('+24 hours')) : null;
            $this->ipAttemptModel->update($attempt['id'], [
                'attempts' => $attempts,
                'last_attempt_at' => $now,
                'blocked_until' => $blockedUntil
            ]);
        } else {
            $this->ipAttemptModel->insert([
                'ip_address' => $ip,
                'attempts' => 1,
                'last_attempt_at' => $now
            ]);
        }
    }

    private function logLoginAttempt($userId, $email, $ipAddress, $userAgent)
    {
        $this->loginActivityModel->insert([
            'user_id' => $userId,
            'email' => $email,
            'login_time' => utc_now(),
            'ip_address' => $ipAddress,
            'user_agent' => $userAgent
        ]);
    }

    private function resetFailedAttempts($userId)
    {
        $this->userModel->update($userId, ['failed_attempts' => 0, 'last_failed_at' => null]);
    }

    protected function getUserRoleNames(int $userId): array
    {
        $userRoleModel = new UserRoleModel();
        $rolesRows = $userRoleModel->select('roles.name')
            ->join('roles', 'roles.id = user_roles.role_id')
            ->where('user_roles.user_id', $userId)
            ->get()
            ->getResultArray();

        return array_column($rolesRows, 'name');
    }

    public function logout()
    {
        // Get user ID and email from session
        $userId = session()->get('user_id');
        $email = session()->get('user_email');

        // Log logout
        $this->logLogout($userId, $email);

        log_message('info', 'Session destroyed.');

        // Destroy the session
        session()->destroy();

        // Redirect to the main login page
        return redirect()->to(base_url('/'));
    }

    private function logLogout($userId, $email)
    {
        $this->loginActivityModel->where('user_id', $userId)
            ->where('logout_time', null)
            ->set('logout_time', utc_now())
            ->set('email', $email)
            ->update();
    }

    private function getUserByEmail($email)
    {
        return $this->userModel->where('email', $email)->first();
    }

    private function checkIfUserSuspended($user)
    {
        return $user['is_suspended'];
    }

    // This function is used to send ResetPassword email to the userwith suspended account.
    private function sendPasswordResetEmail($email)
    {
        // Load the UserController
        $userController = new \App\Controllers\View\UserController();
        // First, attempt to find the user by their email
        $userModel = new UserModel();
        $user = $userModel->where('email', $email)->first();

        if (!$user) {
            log_message('error', 'User with email ' . $email . ' not found for password reset.');
            return false;
        }

        // Generate a secure token for the password reset
        helper('text');
        $token = bin2hex(random_bytes(48));
        $tokenHash = hash('sha256', $token);

        // Calculate the expiration time for the token (1 hour from now)
        $expires_at = Time::now()->addHours(1);

        // Store the token in the password_resets table
        $passwordResetModel = new PasswordResetModel();
        $passwordResetModel->insert([
            'email'      => $email,
            'token'      => $tokenHash,
            'created_at' => Time::now(),
            'expires_at' => $expires_at,
        ]);

        // Now, send the reset email using the generated token
        $userController->sendResetEmail($email, $token); // Calling the original helper function to send the email

        // Log and return success
        log_message('info', 'Password reset email sent to ' . $email);
        return true;
    }

    private function loginUser($user, ?string $redirectTo = null)
    {
        $userRoleModel = new UserRoleModel();
        $roles = $userRoleModel->select('roles.name')
            ->join('roles', 'roles.id = user_roles.role_id')
            ->where('user_roles.user_id', $user['id'])
            ->get()
            ->getResultArray();

        if (empty($roles)) {
            log_message('error', 'No roles found for user ID: ' . $user['id']);
            return redirect()->back()->with('error', 'Role not assigned. Please contact support.');
        }

        $roleNames = array_column($roles, 'name');

        session()->set([
            'user_id'      => $user['id'],
            'user_email'   => $user['email'],
            'user_name'    => $user['firstname'] . ' ' . $user['lastname'],
            'user_type'    => $user['user_type'],
            'is_logged_in' => true,
            'login_time'   => time(),
            'roles'        => $roleNames,
            'semester'     => $this->semester,
            'school_year'  => $this->schoolYear,
        ]);
        $this->applyStylePreferences((int) $user['id']);
        log_message('debug', 'Session after login: ' . print_r(session()->get(), true));
        //dd('Login successful. Roles:', $roleNames, session()->get());

        if (count($roleNames) === 1) {
            // One role → set and redirect directly
            session()->set('role', $roleNames[0]);
            if ($redirectTo !== null) {
                return redirect()->to($redirectTo);
            }
            return $this->redirectToDashboard([$roleNames[0]]);
        } else {
            // Multiple roles → redirect to role selection view
            if ($redirectTo !== null) {
                session()->set('post_login_redirect', $redirectTo);
            } else {
                session()->remove('post_login_redirect');
            }
            return redirect()->to('/select-role');
        }

    }

    private function applyStylePreferences(int $userId): void
    {
        if (!$userId) {
            return;
        }
        $prefs = $this->preferencesModel->where('user_id', $userId)->first();
        if (!$prefs) {
            return;
        }
        $styleCfg = config('Style');

        $styleColor = (string) ($prefs['style_color'] ?? '');
        if ($styleColor !== '' && isset(($styleCfg->stylePalettes ?? [])[$styleColor])) {
            session()->set('style_color', $styleColor);
        }

        $menuColor = (string) ($prefs['menu_color'] ?? '');
        if ($menuColor === 'custom') {
            $bg = (string) ($prefs['menu_custom_bg'] ?? '#0f172a');
            $tx = (string) ($prefs['menu_custom_text'] ?? '#ffffff');
            $mode = (string) ($prefs['menu_custom_mode'] ?? 'dark');
            session()->set('menu_color', 'custom');
            session()->set('menu_custom_bg', $bg);
            session()->set('menu_custom_text', $tx);
            session()->set('menu_custom_mode', $mode);
        } elseif ($menuColor !== '' && isset(($styleCfg->menuPalettes ?? [])[$menuColor])) {
            session()->set('menu_color', $menuColor);
        }
    }

 

private function redirectToDashboard(array $roles)
{
    if (empty($roles)) {
        log_message('error', 'Empty roles array passed to redirectToDashboard.');
        return redirect()->to('/landing_page/guest_dashboard');
    }

    $roleModel = new RoleModel();

    // Resolve all candidate roles (by name or slug), ordered by priority ASC
    $rows = $roleModel->findByNamesOrSlugs($roles);

    if (!empty($rows)) {
        $route = $rows[0]['dashboard_route'] ?? '/landing_page/guest_dashboard';
        log_message('debug', 'Redirecting user to: ' . $route);
        return redirect()->to($route);
    }

    log_message('warning', 'No matching role found. Redirecting to guest dashboard.');
    return redirect()->to('/landing_page/guest_dashboard');
}


    public function setRole()
    {
        $selectedRole = $this->request->getPost('selected_role');
        $availableRoles = session()->get('roles');
        $redirectTo = $this->sanitizeRedirectTarget((string) ($this->request->getPost('redirect_to') ?? session()->get('post_login_redirect') ?? ''));

        if (!$selectedRole || !in_array($selectedRole, $availableRoles)) {
            return redirect()->to('/select-role')->with('error', 'Invalid role selected.');
        }

        session()->set('role', $selectedRole);
        session()->remove('post_login_redirect');
        if ($redirectTo !== null) {
            return redirect()->to($redirectTo);
        }
        return $this->redirectToDashboard([$selectedRole]);
    }

    public function selectRole()
    {
        $roles = session()->get('roles');

        if (!$roles || !is_array($roles)) {
            return redirect()->to('/login')->with('error', 'No roles available.');
        }

        return view('auth/select_role', [
            'roles' => $roles,
            'redirect_to' => (string) (session()->get('post_login_redirect') ?? ''),
        ]);
    }

    private function sanitizeRedirectTarget(string $redirectTo): ?string
    {
        $redirectTo = trim($redirectTo);
        if ($redirectTo === '') {
            return null;
        }

        if (preg_match('#^https?://#i', $redirectTo)) {
            $appHost = (string) parse_url(base_url('/'), PHP_URL_HOST);
            $targetHost = (string) parse_url($redirectTo, PHP_URL_HOST);
            $targetPath = (string) parse_url($redirectTo, PHP_URL_PATH);
            $targetQuery = (string) parse_url($redirectTo, PHP_URL_QUERY);

            if ($appHost === '' || $targetHost === '' || strcasecmp($appHost, $targetHost) !== 0) {
                return null;
            }

            $redirectTo = $targetPath !== '' ? $targetPath : '/';
            if ($targetQuery !== '') {
                $redirectTo .= '?' . $targetQuery;
            }
        }

        if (!str_starts_with($redirectTo, '/')) {
            $redirectTo = '/' . ltrim($redirectTo, '/');
        }

        if (str_starts_with($redirectTo, '//')) {
            return null;
        }

        return $redirectTo;
    }

}
