import re
import secrets
import hashlib
import time
from datetime import datetime, timedelta
from functools import wraps
from flask import request, jsonify, current_app
from flask_login import current_user
import redis
import logging

logger = logging.getLogger(__name__)

# Rate limiting storage (in-memory fallback if Redis not available)
rate_limit_storage = {}

def get_redis_client():
    """Get Redis client for rate limiting (optional)"""
    try:
        import redis
        return redis.Redis(
            host=current_app.config.get('REDIS_HOST', 'localhost'),
            port=current_app.config.get('REDIS_PORT', 6379),
            db=current_app.config.get('REDIS_DB', 0),
            decode_responses=True
        )
    except (ImportError, redis.ConnectionError):
        return None

def rate_limit_check(identifier, action, max_attempts=5, window_minutes=15):
    """
    Check if an action is rate limited
    
    Args:
        identifier: IP address or user ID
        action: Action type (login, register, etc.)
        max_attempts: Maximum attempts allowed
        window_minutes: Time window in minutes
    
    Returns:
        bool: True if action is allowed, False if rate limited
    """
    try:
        redis_client = get_redis_client()
        key = f"rate_limit:{action}:{identifier}"
        
        if redis_client:
            # Use Redis for distributed rate limiting
            current_time = int(time.time())
            window_start = current_time - (window_minutes * 60)
            
            # Remove old entries
            redis_client.zremrangebyscore(key, 0, window_start)
            
            # Count current attempts
            current_attempts = redis_client.zcard(key)
            
            if current_attempts >= max_attempts:
                return False
            
            # Add current attempt
            redis_client.zadd(key, {str(current_time): current_time})
            redis_client.expire(key, window_minutes * 60)
            
            return True
        else:
            # Fallback to in-memory storage
            current_time = datetime.utcnow()
            window_start = current_time - timedelta(minutes=window_minutes)
            
            if key not in rate_limit_storage:
                rate_limit_storage[key] = []
            
            # Remove old entries
            rate_limit_storage[key] = [
                timestamp for timestamp in rate_limit_storage[key]
                if timestamp > window_start
            ]
            
            if len(rate_limit_storage[key]) >= max_attempts:
                return False
            
            # Add current attempt
            rate_limit_storage[key].append(current_time)
            return True
            
    except Exception as e:
        logger.error(f"Rate limit check error: {str(e)}")
        # Allow action if rate limiting fails
        return True

def validate_password_strength(password):
    """
    Validate password strength
    
    Requirements:
    - At least 8 characters long
    - Contains uppercase letter
    - Contains lowercase letter
    - Contains digit
    - Contains special character
    - Not in common weak passwords list
    
    Args:
        password: Password to validate
    
    Returns:
        bool: True if password meets requirements
    """
    if len(password) < 8:
        return False
    
    # Check for required character types
    if not re.search(r'[A-Z]', password):
        return False
    
    if not re.search(r'[a-z]', password):
        return False
    
    if not re.search(r'\d', password):
        return False
    
    if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
        return False
    
    # Check against common weak passwords
    weak_passwords = {
        'password', '12345678', 'qwerty123', 'admin123',
        'password123', '123456789', 'welcome123', 'letmein123',
        'monkey123', 'dragon123', 'master123', 'shadow123'
    }
    
    if password.lower() in weak_passwords:
        return False
    
    return True

def generate_secure_token(length=32):
    """
    Generate cryptographically secure random token
    
    Args:
        length: Token length in bytes
    
    Returns:
        str: Hex-encoded secure token
    """
    return secrets.token_hex(length)

def generate_csrf_token():
    """Generate CSRF token"""
    return generate_secure_token(16)

def validate_csrf_token(token, session_token):
    """Validate CSRF token"""
    return secrets.compare_digest(token, session_token)

def hash_file_content(file_path):
    """
    Calculate SHA1 hash of file content
    
    Args:
        file_path: Path to file
    
    Returns:
        str: SHA1 hash in hexadecimal
    """
    sha1_hash = hashlib.sha1()
    try:
        with open(file_path, 'rb') as f:
            for chunk in iter(lambda: f.read(4096), b""):
                sha1_hash.update(chunk)
        return sha1_hash.hexdigest()
    except Exception as e:
        logger.error(f"File hash calculation error: {str(e)}")
        return None

def validate_file_type(filename, allowed_extensions):
    """
    Validate file type by extension
    
    Args:
        filename: Name of the file
        allowed_extensions: Set of allowed extensions
    
    Returns:
        bool: True if file type is allowed
    """
    if not filename:
        return False
    
    extension = filename.rsplit('.', 1)[-1].lower()
    return extension in allowed_extensions

def sanitize_filename(filename):
    """
    Sanitize filename for safe storage
    
    Args:
        filename: Original filename
    
    Returns:
        str: Sanitized filename
    """
    # Remove path components
    filename = filename.split('/')[-1].split('\\')[-1]
    
    # Replace dangerous characters
    filename = re.sub(r'[^\w\-_\.]', '_', filename)
    
    # Limit length
    if len(filename) > 255:
        name, ext = filename.rsplit('.', 1) if '.' in filename else (filename, '')
        max_name_length = 255 - len(ext) - 1 if ext else 255
        filename = name[:max_name_length] + ('.' + ext if ext else '')
    
    return filename

def validate_ip_address(ip_address):
    """
    Validate IP address format
    
    Args:
        ip_address: IP address string
    
    Returns:
        bool: True if valid IP address
    """
    import ipaddress
    try:
        ipaddress.ip_address(ip_address)
        return True
    except ValueError:
        return False

def is_safe_url(target):
    """
    Check if URL is safe for redirect
    
    Args:
        target: Target URL
    
    Returns:
        bool: True if URL is safe
    """
    from urllib.parse import urlparse, urljoin
    from flask import request
    
    ref_url = urlparse(request.host_url)
    test_url = urlparse(urljoin(request.host_url, target))
    
    return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc

def require_admin(f):
    """
    Decorator to require admin privileges
    
    Args:
        f: Function to decorate
    
    Returns:
        Decorated function
    """
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not current_user.is_authenticated or not current_user.is_admin:
            return jsonify({'error': 'Admin privileges required'}), 403
        return f(*args, **kwargs)
    return decorated_function

def require_active_user(f):
    """
    Decorator to require active user
    
    Args:
        f: Function to decorate
    
    Returns:
        Decorated function
    """
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not current_user.is_authenticated or not current_user.is_active:
            return jsonify({'error': 'Active user account required'}), 403
        return f(*args, **kwargs)
    return decorated_function

def validate_file_size(file_size, max_size=None):
    """
    Validate file size
    
    Args:
        file_size: Size of file in bytes
        max_size: Maximum allowed size (defaults to config)
    
    Returns:
        bool: True if file size is acceptable
    """
    if max_size is None:
        max_size = current_app.config.get('MAX_CONTENT_LENGTH', 500 * 1024 * 1024)
    
    return file_size <= max_size

def detect_malicious_content(file_path):
    """
    Basic malicious content detection
    
    Args:
        file_path: Path to file to check
    
    Returns:
        bool: True if potentially malicious content detected
    """
    try:
        # Check file size (extremely large files might be suspicious)
        import os
        file_size = os.path.getsize(file_path)
        if file_size > 1024 * 1024 * 1024:  # 1GB
            return True
        
        # Check for executable signatures in first few bytes
        with open(file_path, 'rb') as f:
            header = f.read(1024)
        
        # Common executable signatures
        malicious_signatures = [
            b'MZ',  # Windows PE
            b'\x7fELF',  # Linux ELF
            b'\xfe\xed\xfa',  # macOS Mach-O
            b'#!/bin/sh',  # Shell script
            b'#!/bin/bash',  # Bash script
            b'<?php',  # PHP script
        ]
        
        for signature in malicious_signatures:
            if header.startswith(signature):
                return True
        
        return False
        
    except Exception as e:
        logger.error(f"Malicious content detection error: {str(e)}")
        # Err on the side of caution
        return True

def generate_api_key():
    """Generate API key for external integrations"""
    return f"fxd_{generate_secure_token(24)}"

def validate_api_key(api_key):
    """
    Validate API key format
    
    Args:
        api_key: API key to validate
    
    Returns:
        bool: True if API key format is valid
    """
    return bool(re.match(r'^fxd_[a-f0-9]{48}$', api_key))

class SecurityHeaders:
    """Security headers middleware"""
    
    @staticmethod
    def add_security_headers(response):
        """Add security headers to response"""
        response.headers['X-Content-Type-Options'] = 'nosniff'
        response.headers['X-Frame-Options'] = 'DENY'
        response.headers['X-XSS-Protection'] = '1; mode=block'
        response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
        response.headers['Content-Security-Policy'] = (
            "default-src 'self'; "
            "script-src 'self' 'unsafe-inline'; "
            "style-src 'self' 'unsafe-inline'; "
            "img-src 'self' data:; "
            "font-src 'self'; "
            "connect-src 'self'"
        )
        return response