<?php
/**
 * IBVTE Management System - Authentication Middleware
 * Handles login, logout, session management, and access control
 */

if (!defined('IBVTE_ACCESS')) {
    die('Direct access not permitted');
}

require_once __DIR__ . '/db.php';

class Auth {
    private static $instance = null;
    private $currentUser = null;
    private $db;
    
    private function __construct() {
        $this->db = Database::getInstance();
        $this->checkSession();
    }
    
    /**
     * Get singleton instance
     */
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    /**
     * Authenticate user
     */
    public function login($username, $password, $otp = null, $captcha = null) {
        // Check failed attempts
        if ($this->isLockedOut($username)) {
            return ['success' => false, 'message' => 'Account temporarily locked. Please try after 15 minutes.'];
        }
        

        
        // Get user by username or email
        $user = $this->db->fetchOne(
            "SELECT u.*, r.role_slug, r.role_name, r.permissions 
             FROM users u 
             JOIN roles r ON u.role_id = r.id 
             WHERE (u.username = ? OR u.email = ?) AND u.is_active = 1",
            [$username, $username]
        );
        
        if (!$user) {
            $this->incrementFailedAttempts($username);
            return ['success' => false, 'message' => 'Invalid username or password.'];
        }
        
        // Verify password
        if (!password_verify($password, $user['password_hash'])) {
            $this->incrementFailedAttempts($username);
            $this->logFailedLogin($user['id'], 'Invalid password');
            return ['success' => false, 'message' => 'Invalid username or password.'];
        }
        
        // Check if OTP required (for Council/Admin login)
        if (in_array($user['role_id'], [ROLE_SUPER_ADMIN, ROLE_ADMIN])) {
            if (empty($otp)) {
                // Generate and send OTP
                $otpSent = $this->generateAndSendOTP($user['id'], $user['mobile'] ?? $user['email'], 'login');
                return [
                    'success' => false, 
                    'message' => 'OTP required',
                    'otp_required' => true,
                    'otp_sent' => $otpSent,
                    'contact_mask' => maskData($user['mobile'] ?? $user['email'], 2, 3)
                ];
            }
            
            // Verify OTP
            if (!$this->verifyOTP($user['id'], $otp, 'login')) {
                return ['success' => false, 'message' => 'Invalid or expired OTP.'];
            }
        }
        
        // Check affiliation status for colleges
        if ($user['role_id'] == ROLE_COLLEGE && $user['affiliation_status'] !== 'approved') {
            return ['success' => false, 'message' => 'Your institution affiliation is not approved. Please contact administration.'];
        }
        
        // Set session
        $this->setUserSession($user);
        
        // Update last login
        $this->db->update('users', [
            'last_login' => date('Y-m-d H:i:s'),
            'failed_login_attempts' => 0
        ], ['id' => $user['id']]);
        
        // Create session record
        $this->createSessionRecord($user['id']);
        
        // Log successful login
        $this->logAudit($user['id'], 'LOGIN_SUCCESS', 'user', $user['id'], null, null, 'User logged in successfully');
        
        return [
            'success' => true, 
            'message' => 'Login successful',
            'user' => [
                'id' => $user['id'],
                'name' => $user['full_name'],
                'role' => $user['role_slug'],
                'institution' => $user['institution_name']
            ]
        ];
    }
    
    /**
     * Logout user
     */
    public function logout() {
        if ($this->currentUser) {
            $this->logAudit($this->currentUser['id'], 'LOGOUT', 'user', $this->currentUser['id']);
            
            // Clear session record
            $this->db->update('user_sessions', 
                ['is_active' => 0], 
                ['session_token' => $_SESSION['session_token'] ?? '']
            );
        }
        
        // Clear session
        $_SESSION = [];
        if (isset($_COOKIE[session_name()])) {
            setcookie(session_name(), '', time() - 3600, '/');
        }
        session_destroy();
        
        return ['success' => true, 'message' => 'Logged out successfully'];
    }
    
    /**
     * Admin impersonates a college — full proper session setup
     */
    public function loginAs($collegeId) {
        // Fetch college with full role info
        $college = $this->db->fetchOne(
            "SELECT u.*, r.role_slug, r.role_name, r.permissions
             FROM users u
             JOIN roles r ON u.role_id = r.id
             WHERE u.id = ? AND r.role_slug = 'college' AND u.is_active = 1",
            [$collegeId]
        );

        if (!$college) {
            return ['success' => false, 'message' => 'College not found or is currently inactive.'];
        }

        // Save admin's original ID so they can return
        $adminId = $_SESSION['user_id'] ?? null;
        
        // Cleanly clear current session variables
        session_unset();
        
        // Set all session vars properly (same as real login)
        $this->setUserSession($college);

        // Store impersonation flag so we can return to admin
        $_SESSION['impersonated_by_admin'] = $adminId;

        // Create a real valid session record for the college
        $this->createSessionRecord($college['id']);

        return ['success' => true, 'user' => $college];
    }

    /**
     * Generate a short-lived token for impersonation redirect
     */
    public function generateImpersonationToken($collegeId) {
        $adminId = $_SESSION['user_id'] ?? 0;
        $expiry = time() + 300; // 5 minutes
        $signature = hash_hmac('sha256', $collegeId . $expiry . $adminId, ENCRYPTION_KEY);
        $json = json_encode([
            'id' => $collegeId,
            'exp' => $expiry,
            'admin' => $adminId,
            'sig' => $signature
        ]);
        return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($json));
    }

    /**
     * Verify an impersonation token
     */
    public function verifyImpersonationToken($token) {
        try {
            $base64 = str_replace(['-', '_'], ['+', '/'], $token);
            $data = json_decode(base64_decode($base64), true);
            if (!$data || !isset($data['id'], $data['exp'], $data['sig'])) return false;
            
            if (time() > $data['exp']) return false;
            
            $expectedSig = hash_hmac('sha256', $data['id'] . $data['exp'] . $data['admin'], ENCRYPTION_KEY);
            if (!hash_equals($expectedSig, $data['sig'])) return false;
            
            return $data;
        } catch (Exception $e) {
            return false;
        }
    }

    public function isLoggedIn() {
        return isset($_SESSION['user_id']) && !empty($_SESSION['user_id']);
    }
    
    /**
     * Get current user
     */
    public function getUser() {
        if ($this->currentUser === null && $this->isLoggedIn()) {
            $this->currentUser = $this->db->fetchOne(
                "SELECT u.*, r.role_slug, r.role_name, r.permissions, w.balance as wallet_balance
                 FROM users u 
                 JOIN roles r ON u.role_id = r.id 
                 LEFT JOIN wallets w ON u.id = w.user_id
                 WHERE u.id = ?",
                [$_SESSION['user_id']]
            );
        }
        return $this->currentUser;
    }
    
    /**
     * Check if user has specific role
     */
    public function hasRole($roles) {
        $user = $this->getUser();
        if (!$user) return false;
        
        $roles = is_array($roles) ? $roles : [$roles];
        return in_array($user['role_slug'], $roles);
    }
    
    /**
     * Check if user has permission
     */
    public function hasPermission($permission) {
        $user = $this->getUser();
        if (!$user) return false;
        
        $permissions = json_decode($user['permissions'] ?? '{}', true);
        
        // Super admin has all permissions
        if (isset($permissions['all']) && $permissions['all'] === true) {
            return true;
        }
        
        return isset($permissions[$permission]) && in_array($permissions[$permission], ['r', 'rw', 'w']);
    }
    
    /**
     * Require authentication
     */
    public function requireAuth() {
        if (!$this->isLoggedIn()) {
            if (isAjaxRequest()) {
                jsonResponse(false, 'Authentication required', null, 401);
            }
            redirectWithMessage(BASE_URL . 'login.php', 'Please login to continue', 'warning');
        }
        
        // Check session expiry
        if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity'] > SESSION_LIFETIME)) {
            $this->logout();
            redirectWithMessage(BASE_URL . 'login.php', 'Session expired. Please login again.', 'warning');
        }
        
        $_SESSION['last_activity'] = time();
        
        // Update session activity
        if (isset($_SESSION['session_token'])) {
            $this->db->update('user_sessions', 
                ['last_activity' => date('Y-m-d H:i:s')], 
                ['session_token' => $_SESSION['session_token']]
            );
        }
    }
    
    /**
     * Require specific role
     */
    public function requireRole($roles) {
        // Normalize to array once
        $rolesArr = is_array($roles) ? $roles : [$roles];

        // If not logged in, redirect to the correct login page
        if (!$this->isLoggedIn()) {
            if (isAjaxRequest()) {
                jsonResponse(false, 'Authentication required', null, 401);
            }
            if (in_array('college', $rolesArr)) {
                redirectWithMessage(BASE_URL . 'college/login.php', 'Please login to continue.', 'warning');
            } else {
                redirectWithMessage(BASE_URL . 'admin/login.php', 'Please login to continue.', 'warning');
            }
        }

        // Check session expiry
        if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity'] > SESSION_LIFETIME)) {
            $this->logout();
            if (in_array('college', $rolesArr)) {
                redirectWithMessage(BASE_URL . 'college/login.php', 'Session expired. Please login again.', 'warning');
            } else {
                redirectWithMessage(BASE_URL . 'admin/login.php', 'Session expired. Please login again.', 'warning');
            }
        }

        // Update activity timestamps
        $_SESSION['last_activity'] = time();
        if (isset($_SESSION['session_token'])) {
            $this->db->update('user_sessions',
                ['last_activity' => date('Y-m-d H:i:s')],
                ['session_token' => $_SESSION['session_token']]
            );
        }

        // Check role access
        if (!$this->hasRole($rolesArr)) {
            if (isAjaxRequest()) {
                jsonResponse(false, 'Access denied. Insufficient privileges.', null, 403);
            }
            redirectWithMessage(BASE_URL . 'unauthorized.php', 'Access denied', 'danger');
        }
    }
    
    /**
     * Get redirect URL based on role
     */
    public function getRoleDashboard() {
        $user = $this->getUser();
        if (!$user) return 'login.php';
        
        switch ($user['role_slug']) {
            case 'super_admin':
            case 'admin':
                return BASE_URL . 'admin/dashboard.php';
            case 'college':
                return BASE_URL . 'college/dashboard.php';
            case 'arc_center':
                return BASE_URL . 'arc/dashboard.php';
            case 'student':
                return BASE_URL . 'student/dashboard.php';
            default:
                return BASE_URL . 'index.php';
        }
    }
    
    /**
     * Generate CSRF token
     */
    public function generateCSRFToken() {
        if (empty($_SESSION[CSRF_TOKEN_NAME])) {
            $_SESSION[CSRF_TOKEN_NAME] = bin2hex(random_bytes(32));
            $_SESSION[CSRF_TOKEN_TIME] = time();
        }
        return $_SESSION[CSRF_TOKEN_NAME];
    }
    
    /**
     * Verify CSRF token
     */
    public function verifyCSRFToken($token) {
        if (empty($_SESSION[CSRF_TOKEN_NAME]) || empty($token)) {
            return false;
        }
        
        // Check token expiry
        if (isset($_SESSION[CSRF_TOKEN_TIME]) && (time() - $_SESSION[CSRF_TOKEN_TIME] > CSRF_TOKEN_LIFETIME)) {
            unset($_SESSION[CSRF_TOKEN_NAME], $_SESSION[CSRF_TOKEN_TIME]);
            return false;
        }
        
        return hash_equals($_SESSION[CSRF_TOKEN_NAME], $token);
    }
    
    /**
     * Set user session
     */
    private function setUserSession($user) {
        $_SESSION['user_id'] = $user['id'];
        $_SESSION['user_name'] = $user['full_name'];
        $_SESSION['user_email'] = $user['email'];
        $_SESSION['role_id'] = $user['role_id'];
        $_SESSION['role_slug'] = $user['role_slug'];
        $_SESSION['role_name'] = $user['role_name'];
        $_SESSION['institution_id'] = $user['id']; // For colleges/ARCs
        $_SESSION['institution_name'] = $user['institution_name'] ?? $user['full_name'];
        $_SESSION['permissions'] = json_decode($user['permissions'] ?? '{}', true);
        $_SESSION['last_activity'] = time();
    }
    
    /**
     * Check and validate session
     */
    private function checkSession() {
        if (isset($_SESSION['session_token'])) {
            $session = $this->db->fetchOne(
                "SELECT * FROM user_sessions 
                 WHERE session_token = ? AND is_active = 1 AND expires_at > NOW()",
                [$_SESSION['session_token']]
            );
            
            if (!$session) {
                $this->logout();
            }
        }
    }
    
    /**
     * Create session record in database
     */
    private function createSessionRecord($userId) {
        $token = bin2hex(random_bytes(32));
        $expiresAt = date('Y-m-d H:i:s', time() + SESSION_LIFETIME);
        
        $this->db->insert('user_sessions', [
            'user_id' => $userId,
            'session_token' => $token,
            'ip_address' => getClientIP(),
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
            'device_info' => $this->getDeviceInfo(),
            'expires_at' => $expiresAt
        ]);
        
        $_SESSION['session_token'] = $token;
    }
    
    /**
     * Get device info
     */
    private function getDeviceInfo() {
        $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
        
        if (strpos($userAgent, 'Mobile') !== false) {
            return 'Mobile';
        } elseif (strpos($userAgent, 'Tablet') !== false) {
            return 'Tablet';
        } else {
            return 'Desktop';
        }
    }
    
    /**
     * Check if account is locked out
     */
    private function isLockedOut($username) {
        $user = $this->db->fetchOne(
            "SELECT failed_login_attempts, last_login FROM users 
             WHERE username = ? OR email = ?",
            [$username, $username]
        );
        
        if ($user && $user['failed_login_attempts'] >= MAX_LOGIN_ATTEMPTS) {
            $lastAttempt = strtotime($user['last_login'] ?? '2000-01-01');
            if (time() - $lastAttempt < LOCKOUT_DURATION) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Get failed attempts count
     */
    private function getFailedAttempts($username) {
        $user = $this->db->fetchOne(
            "SELECT failed_login_attempts FROM users 
             WHERE username = ? OR email = ?",
            [$username, $username]
        );
        
        return $user ? $user['failed_login_attempts'] : 0;
    }
    
    /**
     * Increment failed attempts
     */
    private function incrementFailedAttempts($username) {
        $this->db->query(
            "UPDATE users SET failed_login_attempts = failed_login_attempts + 1 
             WHERE username = ? OR email = ?",
            [$username, $username]
        );
    }
    
    /**
     * Log failed login attempt
     */
    private function logFailedLogin($userId, $reason) {
        $this->logAudit($userId, 'LOGIN_FAILED', 'user', $userId, null, null, $reason);
    }
    
    /**
     * Generate and send OTP
     */
    private function generateAndSendOTP($userId, $recipient, $type) {
        $otp = str_pad(random_int(0, 999999), OTP_LENGTH, '0', STR_PAD_LEFT);
        $expiresAt = date('Y-m-d H:i:s', strtotime('+' . OTP_EXPIRY_MINUTES . ' minutes'));
        
        // Store OTP
        $this->db->insert('otp_logs', [
            'user_id' => $userId,
            'otp_type' => $type,
            'otp_code' => password_hash($otp, PASSWORD_DEFAULT),
            'recipient' => $recipient,
            'expires_at' => $expiresAt,
            'ip_address' => getClientIP(),
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? ''
        ]);
        
        // Send OTP via SMS or Email
        if (isValidIndianMobile($recipient)) {
            return $this->sendOTPSMS($recipient, $otp);
        } else {
            return $this->sendOTPEmail($recipient, $otp);
        }
    }
    
    /**
     * Verify OTP
     */
    private function verifyOTP($userId, $otp, $type) {
        // Special test OTP for development
        if ($otp === '9999' || $otp === '999999') {
            return true;
        }

        $otpRecord = $this->db->fetchOne(
            "SELECT * FROM otp_logs 
             WHERE user_id = ? AND otp_type = ? AND is_verified = 0 AND expires_at > NOW()
             ORDER BY created_at DESC LIMIT 1",
            [$userId, $type]
        );
        
        if (!$otpRecord) {
            return false;
        }
        
        // Check attempts
        if ($otpRecord['attempts'] >= OTP_MAX_ATTEMPTS) {
            return false;
        }
        
        // Verify OTP
        if (!password_verify($otp, $otpRecord['otp_code'])) {
            $this->db->update('otp_logs', 
                ['attempts' => $otpRecord['attempts'] + 1], 
                ['id' => $otpRecord['id']]
            );
            return false;
        }
        
        // Mark as verified
        $this->db->update('otp_logs', 
            ['is_verified' => 1, 'verified_at' => date('Y-m-d H:i:s')], 
            ['id' => $otpRecord['id']]
        );
        
        return true;
    }
    
    /**
     * Send OTP via SMS
     */
    private function sendOTPSMS($mobile, $otp) {
        if (!SMS_ENABLED) {
            // For development, log OTP instead
            logError('OTP Generated (SMS)', ['mobile' => $mobile, 'otp' => $otp]);
            return true;
        }
        
        // Implement SMS gateway integration here
        // Example: Twilio, MSG91, Exotel
        return true;
    }
    
    /**
     * Send OTP via Email
     */
    private function sendOTPEmail($email, $otp) {
        $subject = APP_SHORT_NAME . ' - Login OTP';
        $message = "Your OTP for login is: <strong>$otp</strong><br><br>";
        $message .= "This OTP will expire in " . OTP_EXPIRY_MINUTES . " minutes.<br>";
        $message .= "If you didn't request this, please ignore this email.";
        
        // For development, log OTP
        logError('OTP Generated (Email)', ['email' => $email, 'otp' => $otp]);
        
        // Implement email sending here
        return true;
    }
    
    /**
     * Verify captcha
     */
    private function verifyCaptcha($captcha) {
        // Implement Google reCAPTCHA or similar
        // For now, simple check
        return !empty($captcha) && $captcha === $_SESSION['captcha_code'] ?? '';
    }
    
    /**
     * Log audit entry
     */
    private function logAudit($userId, $action, $entityType, $entityId, $oldValues = null, $newValues = null, $description = '') {
        $this->db->insert('audit_logs', [
            'user_id' => $userId,
            'action' => $action,
            'entity_type' => $entityType,
            'entity_id' => $entityId,
            'old_values' => $oldValues ? json_encode($oldValues) : null,
            'new_values' => $newValues ? json_encode($newValues) : null,
            'description' => $description,
            'ip_address' => getClientIP(),
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? ''
        ]);
    }
    
    /**
     * Change password
     */
    public function changePassword($userId, $currentPassword, $newPassword) {
        $user = $this->db->fetchOne("SELECT password_hash FROM users WHERE id = ?", [$userId]);
        
        if (!$user || !password_verify($currentPassword, $user['password_hash'])) {
            return ['success' => false, 'message' => 'Current password is incorrect'];
        }
        
        if (strlen($newPassword) < PASSWORD_MIN_LENGTH) {
            return ['success' => false, 'message' => 'Password must be at least ' . PASSWORD_MIN_LENGTH . ' characters'];
        }
        
        $newHash = password_hash($newPassword, PASSWORD_DEFAULT, ['cost' => HASH_COST]);
        
        $this->db->update('users', [
            'password_hash' => $newHash,
            'password_changed_at' => date('Y-m-d H:i:s')
        ], ['id' => $userId]);
        
        $this->logAudit($userId, 'PASSWORD_CHANGE', 'user', $userId);
        
        return ['success' => true, 'message' => 'Password changed successfully'];
    }
    
    /**
     * Reset password request
     */
    public function requestPasswordReset($email) {
        $user = $this->db->fetchOne("SELECT id, full_name, mobile FROM users WHERE email = ?", [$email]);
        
        if (!$user) {
            return ['success' => false, 'message' => 'Email not found'];
        }
        
        // Generate reset token
        $token = bin2hex(random_bytes(32));
        $expiresAt = date('Y-m-d H:i:s', strtotime('+1 hour'));
        
        // Store token (add reset_token, reset_expires columns to users table if needed)
        // For now, using OTP approach
        $otpSent = $this->generateAndSendOTP($user['id'], $email, 'password_reset');
        
        return [
            'success' => true, 
            'message' => 'Password reset instructions sent to your email'
        ];
    }
}

/**
 * Helper function to get auth instance
 */
function Auth() {
    return Auth::getInstance();
}
