<?php
/**
 * IBVTE Management System - Middleware Handler
 * OTP Verification, Captcha Generation, Request Validation
 */

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

require_once __DIR__ . '/config.php';
require_once __DIR__ . '/includes/db.php';

class Middleware {
    private $db;
    private static $instance = null;
    
    private function __construct() {
        $this->db = Database::getInstance();
    }
    
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    // ============================================
    // OTP HANDLING
    // ============================================
    
    /**
     * Generate and send OTP
     */
    public function generateOTP($userId, $contact, $type = 'login', $customMessage = null) {
        // Validate contact
        $isMobile = isValidIndianMobile($contact);
        $isEmail = isValidEmail($contact);
        
        if (!$isMobile && !$isEmail) {
            return ['success' => false, 'message' => 'Invalid contact information'];
        }
        
        // Generate 6-digit OTP
        $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 in database
        $this->db->insert('otp_logs', [
            'user_id' => $userId,
            'otp_type' => $type,
            'otp_code' => password_hash($otp, PASSWORD_DEFAULT),
            'recipient' => $contact,
            'expires_at' => $expiresAt,
            'ip_address' => getClientIP(),
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? ''
        ]);
        
        // Send OTP
        if ($isMobile) {
            $sent = $this->sendSMS($contact, $otp, $customMessage);
        } else {
            $sent = $this->sendEmailOTP($contact, $otp, $type, $customMessage);
        }
        
        if ($sent) {
            logError('OTP Generated', [
                'contact' => maskData($contact, 2, 3),
                'type' => $type,
                'otp' => ENVIRONMENT === 'development' ? $otp : 'HIDDEN'
            ]);
            
            return [
                'success' => true,
                'message' => 'OTP sent successfully',
                'contact_mask' => maskData($contact, 2, 3),
                'expires_in' => OTP_EXPIRY_MINUTES * 60,
                'dev_otp' => ENVIRONMENT === 'development' ? $otp : null // Only for testing
            ];
        }
        
        return ['success' => false, 'message' => 'Failed to send OTP. Please try again.'];
    }
    
    /**
     * Verify OTP
     */
    public function verifyOTP($userId, $otpCode, $type = 'login') {
        if (empty($otpCode) || strlen($otpCode) !== OTP_LENGTH) {
            return ['success' => false, 'message' => 'Invalid OTP format'];
        }
        
        // Get latest unverified OTP
        $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 ['success' => false, 'message' => 'OTP expired or invalid. Please request a new OTP.'];
        }
        
        // Check max attempts
        if ($otpRecord['attempts'] >= OTP_MAX_ATTEMPTS) {
            return ['success' => false, 'message' => 'Maximum attempts exceeded. Please request a new OTP.'];
        }
        
        // Verify OTP code
        if (!password_verify($otpCode, $otpRecord['otp_code'])) {
            // Increment attempts
            $this->db->update('otp_logs', 
                ['attempts' => $otpRecord['attempts'] + 1], 
                ['id' => $otpRecord['id']]
            );
            
            $remainingAttempts = OTP_MAX_ATTEMPTS - ($otpRecord['attempts'] + 1);
            return [
                'success' => false, 
                'message' => "Invalid OTP. $remainingAttempts attempts remaining."
            ];
        }
        
        // Mark as verified
        $this->db->update('otp_logs', 
            [
                'is_verified' => 1, 
                'verified_at' => date('Y-m-d H:i:s')
            ], 
            ['id' => $otpRecord['id']]
        );
        
        return ['success' => true, 'message' => 'OTP verified successfully'];
    }
    
    /**
     * Resend OTP
     */
    public function resendOTP($userId, $contact, $type = 'login') {
        // Check last OTP time to prevent spam
        $lastOTP = $this->db->fetchOne(
            "SELECT created_at FROM otp_logs 
             WHERE user_id = ? AND otp_type = ? 
             ORDER BY created_at DESC LIMIT 1",
            [$userId, $type]
        );
        
        if ($lastOTP) {
            $lastTime = strtotime($lastOTP['created_at']);
            $timeDiff = time() - $lastTime;
            
            if ($timeDiff < 60) { // 1 minute cooldown
                $waitTime = 60 - $timeDiff;
                return [
                    'success' => false, 
                    'message' => "Please wait {$waitTime} seconds before requesting new OTP."
                ];
            }
        }
        
        // Invalidate old OTPs
        $this->db->update('otp_logs', 
            ['is_verified' => 1], // Mark as used to invalidate
            ['user_id' => $userId, 'otp_type' => $type, 'is_verified' => 0]
        );
        
        // Generate new OTP
        return $this->generateOTP($userId, $contact, $type, 'Resend OTP for ' . $type);
    }
    
    /**
     * Send OTP via SMS
     */
    private function sendSMS($mobile, $otp, $customMessage = null) {
        if (!SMS_ENABLED) {
            // Log for development
            logError('SMS OTP (Development Mode)', [
                'mobile' => $mobile,
                'otp' => $otp,
                'message' => $customMessage
            ]);
            return true;
        }
        
        $message = $customMessage ?? "Your IBVTE verification code is: $otp. Valid for " . OTP_EXPIRY_MINUTES . " minutes. Do not share this code.";
        
        // Implement SMS gateway
        try {
            switch (SMS_PROVIDER) {
                case 'msg91':
                    return $this->sendMSG91($mobile, $message);
                case 'twilio':
                    return $this->sendTwilio($mobile, $message);
                default:
                    return false;
            }
        } catch (Exception $e) {
            logError('SMS sending failed', ['error' => $e->getMessage(), 'mobile' => $mobile]);
            return false;
        }
    }
    
    /**
     * Send OTP via Email
     */
    private function sendEmailOTP($email, $otp, $type, $customMessage = null) {
        $subject = APP_SHORT_NAME . ' - Verification Code';
        
        $typeLabels = [
            'login' => 'Login Verification',
            'password_reset' => 'Password Reset',
            'fund_request' => 'Fund Request Verification',
            'wallet_transfer' => 'Wallet Transfer Verification',
            'registration' => 'Account Registration'
        ];
        
        $html = "
        <html>
        <body style='font-family: Arial, sans-serif; background-color: #f4f4f4; padding: 20px;'>
            <div style='max-width: 600px; margin: 0 auto; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 2px 10px rgba(0,0,0,0.1);'>
                <div style='background: linear-gradient(135deg, #3b0b7c 0%, #6c4ab6 100%); padding: 30px; text-align: center;'>
                    <h2 style='color: white; margin: 0;'>" . APP_SHORT_NAME . "</h2>
                    <p style='color: #ffd700; margin: 10px 0 0 0;'>" . ($typeLabels[$type] ?? 'Verification') . "</p>
                </div>
                <div style='padding: 40px 30px;'>
                    <p style='color: #333; font-size: 16px;'>Hello,</p>
                    <p style='color: #555; font-size: 15px; line-height: 1.6;'>
                        Your verification code is:
                    </p>
                    <div style='background: #f8f9fa; border: 2px dashed #3b0b7c; padding: 20px; text-align: center; margin: 20px 0; border-radius: 8px;'>
                        <span style='font-size: 32px; font-weight: bold; color: #3b0b7c; letter-spacing: 8px;'>$otp</span>
                    </div>
                    <p style='color: #777; font-size: 14px;'>
                        This code will expire in <strong>" . OTP_EXPIRY_MINUTES . " minutes</strong>.<br>
                        If you didn't request this code, please ignore this email.
                    </p>
                    <hr style='border: none; border-top: 1px solid #eee; margin: 30px 0;'>
                    <p style='color: #999; font-size: 12px; text-align: center;'>
                        " . INSTITUTION_ADDRESS . "<br>
                        Contact: " . INSTITUTION_PHONE . "
                    </p>
                </div>
            </div>
        </body>
        </html>
        ";
        
        // For development, log the OTP
        logError('Email OTP (Development Mode)', [
            'email' => $email,
            'otp' => $otp,
            'type' => $type
        ]);
        
        // TODO: Implement PHPMailer for production
        // return $this->sendPHPMailer($email, $subject, $html);
        
        return true;
    }
    
    // ============================================
    // CAPTCHA HANDLING
    // ============================================
    
    /**
     * Generate Captcha Image
     */
    public function generateCaptcha() {
        // Generate random code
        $code = generateRandomString(6);
        $_SESSION['captcha_code'] = $code;
        $_SESSION['captcha_time'] = time();
        
        // Create image
        $width = 150;
        $height = 50;
        $image = imagecreatetruecolor($width, $height);
        
        // Colors
        $bgColor = imagecolorallocate($image, 59, 11, 124); // IBVTE Purple
        $textColor = imagecolorallocate($image, 255, 255, 255);
        $lineColor = imagecolorallocate($image, 108, 74, 182);
        $pixelColor = imagecolorallocate($image, 200, 200, 200);
        
        // Fill background
        imagefilledrectangle($image, 0, 0, $width, $height, $bgColor);
        
        // Add noise - pixels
        for ($i = 0; $i < 100; $i++) {
            imagesetpixel($image, rand(0, $width), rand(0, $height), $pixelColor);
        }
        
        // Add noise - lines
        for ($i = 0; $i < 5; $i++) {
            imageline($image, 0, rand(0, $height), $width, rand(0, $height), $lineColor);
        }
        
        // Add text
        $fontSize = 20;
        $angle = rand(-5, 5);
        $x = 20;
        $y = 35;
        
        imagettftext($image, $fontSize, $angle, $x, $y, $textColor, __DIR__ . '/assets/fonts/arial.ttf', $code);
        
        // Output image
        header('Content-Type: image/png');
        imagepng($image);
        imagedestroy($image);
        exit;
    }
    
    /**
     * Generate Simple Math Captcha
     */
    public function generateMathCaptcha() {
        $operators = ['+', '-', '*'];
        $operator = $operators[array_rand($operators)];
        
        switch ($operator) {
            case '+':
                $a = rand(1, 50);
                $b = rand(1, 50);
                $answer = $a + $b;
                break;
            case '-':
                $a = rand(20, 99);
                $b = rand(1, 20);
                $answer = $a - $b;
                break;
            case '*':
                $a = rand(2, 12);
                $b = rand(2, 10);
                $answer = $a * $b;
                break;
        }
        
        $_SESSION['captcha_answer'] = $answer;
        $_SESSION['captcha_time'] = time();
        
        return [
            'question' => "$a $operator $b = ?",
            'a' => $a,
            'b' => $b,
            'operator' => $operator
        ];
    }
    
    /**
     * Verify Captcha
     */
    public function verifyCaptcha($userInput, $type = 'text') {
        if (empty($userInput)) {
            return ['success' => false, 'message' => 'Captcha is required'];
        }
        
        // Check captcha expiry (5 minutes)
        if (!isset($_SESSION['captcha_time']) || (time() - $_SESSION['captcha_time'] > 300)) {
            return ['success' => false, 'message' => 'Captcha expired. Please refresh.'];
        }
        
        if ($type === 'math') {
            $expected = $_SESSION['captcha_answer'] ?? null;
        } else {
            $expected = $_SESSION['captcha_code'] ?? null;
        }
        
        if ($expected === null) {
            return ['success' => false, 'message' => 'Captcha not generated. Please refresh.'];
        }
        
        // Verify
        if (strcasecmp(trim($userInput), (string)$expected) === 0) {
            // Clear captcha after successful verification
            unset($_SESSION['captcha_code'], $_SESSION['captcha_answer'], $_SESSION['captcha_time']);
            return ['success' => true, 'message' => 'Captcha verified'];
        }
        
        return ['success' => false, 'message' => 'Invalid captcha. Please try again.'];
    }
    
    /**
     * Check if captcha required (after failed attempts)
     */
    public function isCaptchaRequired($identifier) {
        $attempts = $_SESSION['failed_attempts'][$identifier] ?? 0;
        return $attempts >= 3;
    }
    
    /**
     * Record failed attempt
     */
    public function recordFailedAttempt($identifier) {
        if (!isset($_SESSION['failed_attempts'])) {
            $_SESSION['failed_attempts'] = [];
        }
        $_SESSION['failed_attempts'][$identifier] = ($_SESSION['failed_attempts'][$identifier] ?? 0) + 1;
    }
    
    /**
     * Reset failed attempts
     */
    public function resetFailedAttempts($identifier) {
        unset($_SESSION['failed_attempts'][$identifier]);
    }
    
    // ============================================
    // REQUEST VALIDATION
    // ============================================
    
    /**
     * Validate request data
     */
    public function validate($data, $rules) {
        $errors = [];
        
        foreach ($rules as $field => $ruleSet) {
            $value = $data[$field] ?? null;
            $rulesList = explode('|', $ruleSet);
            
            foreach ($rulesList as $rule) {
                $parts = explode(':', $rule);
                $ruleName = $parts[0];
                $ruleParam = $parts[1] ?? null;
                
                switch ($ruleName) {
                    case 'required':
                        if (empty($value) && $value !== '0') {
                            $errors[$field][] = "$field is required";
                        }
                        break;
                        
                    case 'email':
                        if (!empty($value) && !isValidEmail($value)) {
                            $errors[$field][] = "Invalid email format";
                        }
                        break;
                        
                    case 'mobile':
                        if (!empty($value) && !isValidIndianMobile($value)) {
                            $errors[$field][] = "Invalid mobile number";
                        }
                        break;
                        
                    case 'min':
                        if (!empty($value) && strlen($value) < (int)$ruleParam) {
                            $errors[$field][] = "Minimum $ruleParam characters required";
                        }
                        break;
                        
                    case 'max':
                        if (!empty($value) && strlen($value) > (int)$ruleParam) {
                            $errors[$field][] = "Maximum $ruleParam characters allowed";
                        }
                        break;
                        
                    case 'numeric':
                        if (!empty($value) && !is_numeric($value)) {
                            $errors[$field][] = "Must be numeric";
                        }
                        break;
                        
                    case 'date':
                        if (!empty($value) && !strtotime($value)) {
                            $errors[$field][] = "Invalid date format";
                        }
                        break;
                        
                    case 'in':
                        $allowed = explode(',', $ruleParam);
                        if (!empty($value) && !in_array($value, $allowed)) {
                            $errors[$field][] = "Invalid value selected";
                        }
                        break;
                        
                    case 'match':
                        $matchValue = $data[$ruleParam] ?? null;
                        if ($value !== $matchValue) {
                            $errors[$field][] = "Does not match with $ruleParam";
                        }
                        break;
                        
                    case 'unique':
                        // Check database uniqueness
                        // Format: unique:table,column,exceptId
                        $tableInfo = explode(',', $ruleParam);
                        $table = $tableInfo[0];
                        $column = $tableInfo[1] ?? $field;
                        $exceptId = $tableInfo[2] ?? null;
                        
                        if (!empty($value)) {
                            $sql = "SELECT COUNT(*) FROM $table WHERE $column = ?";
                            $params = [$value];
                            
                            if ($exceptId) {
                                $sql .= " AND id != ?";
                                $params[] = $exceptId;
                            }
                            
                            $count = $this->db->fetchColumn($sql, $params);
                            if ($count > 0) {
                                $errors[$field][] = "This $field already exists";
                            }
                        }
                        break;
                }
            }
        }
        
        return [
            'valid' => empty($errors),
            'errors' => $errors
        ];
    }
    
    /**
     * Sanitize input data
     */
    public function sanitize($data) {
        if (is_array($data)) {
            return array_map([$this, 'sanitize'], $data);
        }
        
        $data = trim($data);
        $data = stripslashes($data);
        $data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
        return $data;
    }
    
    /**
     * Rate limiting check
     */
    public function checkRateLimit($identifier, $maxRequests = 5, $windowSeconds = 60) {
        $key = 'rate_limit_' . $identifier;
        $now = time();
        
        if (!isset($_SESSION[$key])) {
            $_SESSION[$key] = [];
        }
        
        // Remove old entries outside window
        $_SESSION[$key] = array_filter($_SESSION[$key], function($timestamp) use ($now, $windowSeconds) {
            return ($now - $timestamp) < $windowSeconds;
        });
        
        // Check limit
        if (count($_SESSION[$key]) >= $maxRequests) {
            $oldest = min($_SESSION[$key]);
            $waitTime = $windowSeconds - ($now - $oldest);
            return [
                'allowed' => false,
                'message' => "Rate limit exceeded. Please wait $waitTime seconds."
            ];
        }
        
        // Add current request
        $_SESSION[$key][] = $now;
        
        return ['allowed' => true];
    }
    
    // ============================================
    // PRIVATE HELPERS
    // ============================================
    
    private function sendMSG91($mobile, $message) {
        // Implement MSG91 integration
        // This is a placeholder
        return true;
    }
    
    private function sendTwilio($mobile, $message) {
        // Implement Twilio integration
        // This is a placeholder
        return true;
    }
}

/**
 * Helper function
 */
function Middleware() {
    return Middleware::getInstance();
}
