# security_utils.py
"""Security utility functions for spam prevention and validation"""

import re
import secrets
import requests
from datetime import datetime, timedelta
from flask import request, current_app
from database import db
from models import FailedLoginAttempt, User
from config.security_config import *

def verify_recaptcha(response):
    """Verify reCAPTCHA response with Google"""
    if not RECAPTCHA_ENABLED:
        return True  # Skip if not configured
    
    if not response:
        return False
    
    payload = {
        'secret': RECAPTCHA_SECRET_KEY,
        'response': response,
        'remoteip': request.remote_addr
    }
    
    try:
        r = requests.post(
            'https://www.google.com/recaptcha/api/siteverify',
            data=payload,
            timeout=5
        )
        result = r.json()
        return result.get('success', False)
    except Exception as e:
        current_app.logger.error(f"reCAPTCHA verification error: {e}")
        return False

def check_honeypot(form_data):
    """Check if honeypot field was filled (indicates bot)"""
    honeypot_value = form_data.get(HONEYPOT_FIELD_NAME, '')
    return honeypot_value == ''  # Should be empty for real users

def is_ip_blocked(ip_address):
    """Check if IP is currently blocked due to failed attempts"""
    cutoff_time = datetime.utcnow() - BLOCK_DURATION
    
    recent_failures = FailedLoginAttempt.query.filter(
        FailedLoginAttempt.ip_address == ip_address,
        FailedLoginAttempt.attempt_time > cutoff_time
    ).count()
    
    return recent_failures >= MAX_FAILED_ATTEMPTS

def record_failed_attempt(ip_address, email=None):
    """Record a failed login/signup attempt"""
    attempt = FailedLoginAttempt(
        ip_address=ip_address,
        email=email,
        user_agent=request.headers.get('User-Agent', '')[:500]
    )
    db.session.add(attempt)
    db.session.commit()

def validate_password_strength(password):
    """Validate password meets security requirements"""
    errors = []
    
    if len(password) < MIN_PASSWORD_LENGTH:
        errors.append(f"Password must be at least {MIN_PASSWORD_LENGTH} characters long")
    
    if REQUIRE_UPPERCASE and not re.search(r'[A-Z]', password):
        errors.append("Password must contain at least one uppercase letter")
    
    if REQUIRE_LOWERCASE and not re.search(r'[a-z]', password):
        errors.append("Password must contain at least one lowercase letter")
    
    if REQUIRE_NUMBERS and not re.search(r'\d', password):
        errors.append("Password must contain at least one number")
    
    if REQUIRE_SPECIAL_CHARS and not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
        errors.append("Password must contain at least one special character")
    
    return errors

def generate_verification_token():
    """Generate a secure random token for email verification"""
    return secrets.token_urlsafe(32)

def send_verification_email(user, token):
    """Send email verification link to user"""
    from email_config import mail
    from flask_mail import Message
    from flask import url_for
    
    verification_url = url_for(
        'verify_email',
        token=token,
        _external=True
    )
    
    try:
        msg = Message(
            'Verify Your EPOLaw Account',
            sender=current_app.config.get('MAIL_USERNAME', 'noreply@epolaw.com'),
            recipients=[user.email]
        )
        msg.html = f'''
        <h2>Welcome to EPOLaw, {user.first_name}!</h2>
        <p>Please verify your email address to activate your account.</p>
        <p><a href="{verification_url}" style="background-color: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;">Verify Email Address</a></p>
        <p>Or copy and paste this link into your browser:</p>
        <p>{verification_url}</p>
        <p>This link will expire in 24 hours.</p>
        <p>If you didn't create an account with EPOLaw, please ignore this email.</p>
        <p>Best regards,<br>The EPOLaw Team</p>
        '''
        mail.send(msg)
        return True
    except Exception as e:
        current_app.logger.error(f"Failed to send verification email: {e}")
        return False

def clean_old_attempts():
    """Clean up old failed login attempts (older than 24 hours)"""
    cutoff_time = datetime.utcnow() - timedelta(hours=24)
    FailedLoginAttempt.query.filter(
        FailedLoginAttempt.attempt_time < cutoff_time
    ).delete()
    db.session.commit()
# Additional security utility functions (Added Dec 2025)

def validate_email(email):
    """
    Validate email address format
    
    Returns:
        tuple: (is_valid, error_message)
    """
    if not email:
        return False, "Email is required"
    
    # RFC 5322 simplified pattern
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    
    if not re.match(pattern, email):
        return False, "Invalid email format"
    
    # Additional checks
    if len(email) > 254:  # RFC 5321
        return False, "Email address is too long"
    
    local, domain = email.rsplit('@', 1)
    if len(local) > 64:  # RFC 5321
        return False, "Email local part is too long"
    
    return True, None


def sanitize_input(text, max_length=None, allow_html=False):
    """
    Sanitize text input to prevent XSS and injection attacks
    
    Args:
        text: Input text to sanitize
        max_length: Maximum allowed length
        allow_html: Whether to allow HTML (will be escaped if False)
    
    Returns:
        str: Sanitized text
    """
    if not text:
        return ''
    
    # Convert to string
    text = str(text)
    
    # Truncate if needed
    if max_length and len(text) > max_length:
        text = text[:max_length]
    
    # Remove null bytes
    text = text.replace('\x00', '')
    
    # If HTML not allowed, escape it
    if not allow_html:
        # Basic HTML escaping
        text = text.replace('&', '&amp;')
        text = text.replace('<', '&lt;')
        text = text.replace('>', '&gt;')
        text = text.replace('"', '&quot;')
        text = text.replace("'", '&#x27;')
    
    return text


def log_security_event(event_type, description, user_id=None, severity='INFO'):
    """
    Log security-related events
    
    Args:
        event_type: Type of security event (login_failed, unauthorized_access, etc.)
        description: Detailed description
        user_id: User ID if applicable
        severity: Log severity (INFO, WARNING, ERROR, CRITICAL)
    """
    import logging
    ip_address = request.remote_addr if request else 'unknown'
    user_agent = request.headers.get('User-Agent', 'unknown') if request else 'unknown'
    
    log_entry = {
        'timestamp': datetime.utcnow().isoformat(),
        'event_type': event_type,
        'description': description,
        'user_id': user_id,
        'ip_address': ip_address,
        'user_agent': user_agent,
        'severity': severity
    }
    
    # Log to security log
    security_logger = logging.getLogger('security')
    
    if severity == 'CRITICAL':
        security_logger.critical(f"SECURITY: {event_type} - {description} - User: {user_id} - IP: {ip_address}")
    elif severity == 'ERROR':
        security_logger.error(f"SECURITY: {event_type} - {description} - User: {user_id} - IP: {ip_address}")
    elif severity == 'WARNING':
        security_logger.warning(f"SECURITY: {event_type} - {description} - User: {user_id} - IP: {ip_address}")
    else:
        security_logger.info(f"SECURITY: {event_type} - {description} - User: {user_id} - IP: {ip_address}")
    
    return log_entry


def is_safe_redirect_url(target):
    """
    Check if redirect URL is safe (prevents open redirect vulnerabilities)
    
    Returns:
        bool: True if safe, False otherwise
    """
    if not target:
        return False
    
    # Only allow relative URLs (no protocol)
    if '://' in target:
        return False
    
    # Don't allow // (protocol-relative URLs)
    if target.startswith('//'):
        return False
    
    # Don't allow backslashes (Windows-style paths)
    if '\\' in target:
        return False
    
    return True
