"""Accounts Decorators

This module provides decorators specific to the Adtlas Accounts module:
- User authentication and authorization decorators
- Account status validation decorators
- Profile completion decorators
- Email verification decorators
- Account security decorators
- User activity tracking decorators

Usage:
    from apps.accounts.decorators import (
        require_verified_email,
        require_complete_profile,
        require_active_account,
        track_user_activity,
        require_password_change,
        check_account_lockout,
    )

Security:
    All decorators implement security best practices:
    - Secure session handling
    - Account lockout protection
    - Activity logging
    - Input validation
"""

import functools
from datetime import datetime, timedelta
from typing import Any, Callable, Optional, Union

from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required
from django.core.cache import cache
from django.core.exceptions import PermissionDenied
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.shortcuts import redirect
from django.urls import reverse
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.http import require_http_methods
from django.contrib import messages
from django.db import transaction

from apps.core.logging import get_logger, log_user_action, log_security_event
from apps.core.utils import get_client_ip

User = get_user_model()
logger = get_logger('accounts.decorators')


# =============================================================================
# Account Status Decorators
# =============================================================================

def require_active_account(view_func: Callable = None, *, 
                          redirect_url: str = 'accounts:account_inactive'):
    """
    Decorator to ensure user account is active.
    
    Args:
        view_func: View function to decorate
        redirect_url: URL to redirect to if account is inactive
        
    Returns:
        Decorated view function
    """
    def decorator(func):
        @functools.wraps(func)
        @login_required
        def wrapper(request: HttpRequest, *args, **kwargs):
            if not request.user.is_active:
                log_security_event(
                    event_type='inactive_account_access',
                    description=f"Inactive account access attempt: {request.user.username}",
                    severity='MEDIUM',
                    user=request.user,
                    request=request
                )
                
                messages.error(request, "Your account is inactive. Please contact support.")
                return redirect(redirect_url)
            
            return func(request, *args, **kwargs)
        return wrapper
    
    if view_func is None:
        return decorator
    else:
        return decorator(view_func)


def require_verified_email(view_func: Callable = None, *, 
                          redirect_url: str = 'accounts:email_verification_required'):
    """
    Decorator to ensure user's email is verified.
    
    Args:
        view_func: View function to decorate
        redirect_url: URL to redirect to if email is not verified
        
    Returns:
        Decorated view function
    """
    def decorator(func):
        @functools.wraps(func)
        @login_required
        def wrapper(request: HttpRequest, *args, **kwargs):
            # Check if user has verified email (assuming email_verified field exists)
            if hasattr(request.user, 'email_verified') and not request.user.email_verified:
                messages.warning(request, "Please verify your email address to continue.")
                return redirect(redirect_url)
            
            return func(request, *args, **kwargs)
        return wrapper
    
    if view_func is None:
        return decorator
    else:
        return decorator(view_func)


def require_complete_profile(view_func: Callable = None, *, 
                           redirect_url: str = 'accounts:profile_complete',
                           required_fields: list = None):
    """
    Decorator to ensure user profile is complete.
    
    Args:
        view_func: View function to decorate
        redirect_url: URL to redirect to if profile is incomplete
        required_fields: List of required profile fields
        
    Returns:
        Decorated view function
    """
    if required_fields is None:
        required_fields = ['first_name', 'last_name']
    
    def decorator(func):
        @functools.wraps(func)
        @login_required
        def wrapper(request: HttpRequest, *args, **kwargs):
            user = request.user
            
            # Check required fields
            missing_fields = []
            for field in required_fields:
                if not getattr(user, field, None):
                    missing_fields.append(field)
            
            if missing_fields:
                messages.info(
                    request, 
                    f"Please complete your profile. Missing: {', '.join(missing_fields)}"
                )
                return redirect(redirect_url)
            
            return func(request, *args, **kwargs)
        return wrapper
    
    if view_func is None:
        return decorator
    else:
        return decorator(view_func)


def check_account_lockout(view_func: Callable = None, *, 
                         redirect_url: str = 'accounts:account_locked'):
    """
    Decorator to check if account is locked due to failed login attempts.
    
    Args:
        view_func: View function to decorate
        redirect_url: URL to redirect to if account is locked
        
    Returns:
        Decorated view function
    """
    def decorator(func):
        @functools.wraps(func)
        @login_required
        def wrapper(request: HttpRequest, *args, **kwargs):
            # Check if account is locked
            cache_key = f"failed_login_attempts_{request.user.username}"
            attempts = cache.get(cache_key, 0)
            max_attempts = getattr(settings, 'MAX_LOGIN_ATTEMPTS', 5)
            
            if attempts >= max_attempts:
                log_security_event(
                    event_type='locked_account_access',
                    description=f"Locked account access attempt: {request.user.username}",
                    severity='HIGH',
                    user=request.user,
                    request=request
                )
                
                messages.error(request, "Your account is temporarily locked. Please try again later.")
                return redirect(redirect_url)
            
            return func(request, *args, **kwargs)
        return wrapper
    
    if view_func is None:
        return decorator
    else:
        return decorator(view_func)


def require_password_change(view_func: Callable = None, *, 
                           redirect_url: str = 'accounts:password_change_required',
                           max_age_days: int = 90):
    """
    Decorator to require password change if password is too old.
    
    Args:
        view_func: View function to decorate
        redirect_url: URL to redirect to for password change
        max_age_days: Maximum age of password in days
        
    Returns:
        Decorated view function
    """
    def decorator(func):
        @functools.wraps(func)
        @login_required
        def wrapper(request: HttpRequest, *args, **kwargs):
            user = request.user
            
            # Check if user has password_changed_date field
            if hasattr(user, 'password_changed_date'):
                if user.password_changed_date:
                    age = timezone.now() - user.password_changed_date
                    if age.days > max_age_days:
                        messages.warning(
                            request, 
                            f"Your password is {age.days} days old. Please change it for security."
                        )
                        return redirect(redirect_url)
            
            return func(request, *args, **kwargs)
        return wrapper
    
    if view_func is None:
        return decorator
    else:
        return decorator(view_func)


# =============================================================================
# Activity Tracking Decorators
# =============================================================================

def track_user_activity(activity_type: str = 'page_view', 
                       object_type: str = None,
                       track_anonymous: bool = False):
    """
    Decorator to track user activity.
    
    Args:
        activity_type: Type of activity to track
        object_type: Type of object being accessed
        track_anonymous: Whether to track anonymous users
        
    Returns:
        Decorated view function
    """
    def decorator(func):
        @functools.wraps(func)
        def wrapper(request: HttpRequest, *args, **kwargs):
            # Execute the view first
            response = func(request, *args, **kwargs)
            
            # Track activity if user is authenticated or tracking anonymous
            if request.user.is_authenticated or track_anonymous:
                try:
                    # Extract object ID from URL kwargs if available
                    object_id = kwargs.get('pk') or kwargs.get('id')
                    
                    log_user_action(
                        user=request.user if request.user.is_authenticated else None,
                        action=activity_type,
                        object_type=object_type or func.__name__,
                        object_id=object_id,
                        request=request,
                        additional_data={
                            'view_name': func.__name__,
                            'url_path': request.path,
                            'method': request.method,
                        }
                    )
                except Exception as e:
                    logger.error(f"Failed to track user activity: {str(e)}")
            
            return response
        return wrapper
    return decorator


def track_sensitive_action(action_name: str, require_confirmation: bool = False):
    """
    Decorator to track sensitive user actions.
    
    Args:
        action_name: Name of the sensitive action
        require_confirmation: Whether to require additional confirmation
        
    Returns:
        Decorated view function
    """
    def decorator(func):
        @functools.wraps(func)
        @login_required
        def wrapper(request: HttpRequest, *args, **kwargs):
            # Log the sensitive action attempt
            log_security_event(
                event_type='sensitive_action',
                description=f"User {request.user.username} attempting {action_name}",
                severity='MEDIUM',
                user=request.user,
                request=request,
                additional_data={
                    'action_name': action_name,
                    'view_name': func.__name__,
                }
            )
            
            # Check for confirmation if required
            if require_confirmation and request.method == 'POST':
                confirmation = request.POST.get('confirm_action')
                if confirmation != 'yes':
                    messages.error(request, "Action requires confirmation.")
                    return redirect(request.path)
            
            # Execute the view
            response = func(request, *args, **kwargs)
            
            # Log successful completion
            if hasattr(response, 'status_code') and 200 <= response.status_code < 300:
                log_user_action(
                    user=request.user,
                    action=action_name,
                    object_type='SensitiveAction',
                    request=request,
                    additional_data={
                        'action_name': action_name,
                        'view_name': func.__name__,
                        'success': True,
                    }
                )
            
            return response
        return wrapper
    return decorator


# =============================================================================
# Rate Limiting Decorators
# =============================================================================

def rate_limit_user(max_requests: int = 10, window_minutes: int = 1, 
                   block_duration_minutes: int = 5):
    """
    Decorator to rate limit requests per user.
    
    Args:
        max_requests: Maximum requests allowed in window
        window_minutes: Time window in minutes
        block_duration_minutes: How long to block after limit exceeded
        
    Returns:
        Decorated view function
    """
    def decorator(func):
        @functools.wraps(func)
        @login_required
        def wrapper(request: HttpRequest, *args, **kwargs):
            user_id = request.user.id
            cache_key = f"rate_limit_user_{user_id}_{func.__name__}"
            block_key = f"rate_limit_block_{user_id}_{func.__name__}"
            
            # Check if user is currently blocked
            if cache.get(block_key):
                log_security_event(
                    event_type='rate_limit_exceeded',
                    description=f"Rate limit exceeded for user {request.user.username} on {func.__name__}",
                    severity='MEDIUM',
                    user=request.user,
                    request=request
                )
                
                if request.headers.get('Accept') == 'application/json':
                    return JsonResponse(
                        {'error': 'Rate limit exceeded. Please try again later.'}, 
                        status=429
                    )
                else:
                    messages.error(request, "Too many requests. Please try again later.")
                    return redirect('accounts:dashboard')
            
            # Get current request count
            current_count = cache.get(cache_key, 0)
            
            if current_count >= max_requests:
                # Block the user
                cache.set(block_key, True, block_duration_minutes * 60)
                
                log_security_event(
                    event_type='rate_limit_exceeded',
                    description=f"Rate limit exceeded for user {request.user.username} on {func.__name__}",
                    severity='MEDIUM',
                    user=request.user,
                    request=request
                )
                
                if request.headers.get('Accept') == 'application/json':
                    return JsonResponse(
                        {'error': 'Rate limit exceeded. Please try again later.'}, 
                        status=429
                    )
                else:
                    messages.error(request, "Too many requests. Please try again later.")
                    return redirect('accounts:dashboard')
            
            # Increment request count
            cache.set(cache_key, current_count + 1, window_minutes * 60)
            
            return func(request, *args, **kwargs)
        return wrapper
    return decorator


def rate_limit_ip(max_requests: int = 20, window_minutes: int = 1, 
                 block_duration_minutes: int = 5):
    """
    Decorator to rate limit requests per IP address.
    
    Args:
        max_requests: Maximum requests allowed in window
        window_minutes: Time window in minutes
        block_duration_minutes: How long to block after limit exceeded
        
    Returns:
        Decorated view function
    """
    def decorator(func):
        @functools.wraps(func)
        def wrapper(request: HttpRequest, *args, **kwargs):
            ip_address = get_client_ip(request)
            cache_key = f"rate_limit_ip_{ip_address}_{func.__name__}"
            block_key = f"rate_limit_block_ip_{ip_address}_{func.__name__}"
            
            # Check if IP is currently blocked
            if cache.get(block_key):
                log_security_event(
                    event_type='ip_rate_limit_exceeded',
                    description=f"IP rate limit exceeded for {ip_address} on {func.__name__}",
                    severity='MEDIUM',
                    request=request,
                    additional_data={'ip_address': ip_address}
                )
                
                if request.headers.get('Accept') == 'application/json':
                    return JsonResponse(
                        {'error': 'Rate limit exceeded. Please try again later.'}, 
                        status=429
                    )
                else:
                    return HttpResponse("Rate limit exceeded. Please try again later.", status=429)
            
            # Get current request count
            current_count = cache.get(cache_key, 0)
            
            if current_count >= max_requests:
                # Block the IP
                cache.set(block_key, True, block_duration_minutes * 60)
                
                log_security_event(
                    event_type='ip_rate_limit_exceeded',
                    description=f"IP rate limit exceeded for {ip_address} on {func.__name__}",
                    severity='MEDIUM',
                    request=request,
                    additional_data={'ip_address': ip_address}
                )
                
                if request.headers.get('Accept') == 'application/json':
                    return JsonResponse(
                        {'error': 'Rate limit exceeded. Please try again later.'}, 
                        status=429
                    )
                else:
                    return HttpResponse("Rate limit exceeded. Please try again later.", status=429)
            
            # Increment request count
            cache.set(cache_key, current_count + 1, window_minutes * 60)
            
            return func(request, *args, **kwargs)
        return wrapper
    return decorator


# =============================================================================
# Security Decorators
# =============================================================================

def require_secure_connection(view_func: Callable = None):
    """
    Decorator to require HTTPS connection.
    
    Args:
        view_func: View function to decorate
        
    Returns:
        Decorated view function
    """
    def decorator(func):
        @functools.wraps(func)
        def wrapper(request: HttpRequest, *args, **kwargs):
            if not request.is_secure() and not settings.DEBUG:
                log_security_event(
                    event_type='insecure_connection',
                    description=f"Insecure connection attempt to {func.__name__}",
                    severity='MEDIUM',
                    request=request
                )
                
                # Redirect to HTTPS
                secure_url = request.build_absolute_uri().replace('http://', 'https://')
                return redirect(secure_url)
            
            return func(request, *args, **kwargs)
        return wrapper
    
    if view_func is None:
        return decorator
    else:
        return decorator(view_func)


def require_fresh_login(max_age_minutes: int = 30):
    """
    Decorator to require fresh login for sensitive operations.
    
    Args:
        max_age_minutes: Maximum age of login session in minutes
        
    Returns:
        Decorated view function
    """
    def decorator(func):
        @functools.wraps(func)
        @login_required
        def wrapper(request: HttpRequest, *args, **kwargs):
            # Check last login time
            last_login = request.session.get('last_fresh_login')
            
            if last_login:
                last_login_time = datetime.fromisoformat(last_login)
                age = timezone.now() - last_login_time
                
                if age.total_seconds() > (max_age_minutes * 60):
                    # Store the intended URL
                    request.session['next_after_fresh_login'] = request.get_full_path()
                    messages.info(request, "Please re-enter your password for security.")
                    return redirect('accounts:fresh_login')
            else:
                # No fresh login recorded
                request.session['next_after_fresh_login'] = request.get_full_path()
                messages.info(request, "Please re-enter your password for security.")
                return redirect('accounts:fresh_login')
            
            return func(request, *args, **kwargs)
        return wrapper
    return decorator


# =============================================================================
# Class-based View Decorators
# =============================================================================

class AccountsDecoratorMixin:
    """
    Mixin for class-based views to apply accounts decorators.
    """
    
    # Account status requirements
    require_active = False
    require_verified_email = False
    require_complete_profile = False
    required_profile_fields = None
    
    # Security requirements
    require_fresh_login_minutes = None
    require_secure = False
    
    # Rate limiting
    rate_limit_requests = None
    rate_limit_window_minutes = 1
    
    # Activity tracking
    track_activity = True
    activity_type = 'page_view'
    
    def dispatch(self, request, *args, **kwargs):
        # Check account status requirements
        if self.require_active and request.user.is_authenticated:
            if not request.user.is_active:
                messages.error(request, "Your account is inactive.")
                return redirect('accounts:account_inactive')
        
        if self.require_verified_email and request.user.is_authenticated:
            if hasattr(request.user, 'email_verified') and not request.user.email_verified:
                messages.warning(request, "Please verify your email address.")
                return redirect('accounts:email_verification_required')
        
        if self.require_complete_profile and request.user.is_authenticated:
            fields = self.required_profile_fields or ['first_name', 'last_name']
            missing = [f for f in fields if not getattr(request.user, f, None)]
            if missing:
                messages.info(request, f"Please complete your profile. Missing: {', '.join(missing)}")
                return redirect('accounts:profile_complete')
        
        # Check security requirements
        if self.require_secure and not request.is_secure() and not settings.DEBUG:
            secure_url = request.build_absolute_uri().replace('http://', 'https://')
            return redirect(secure_url)
        
        if self.require_fresh_login_minutes and request.user.is_authenticated:
            last_login = request.session.get('last_fresh_login')
            if last_login:
                last_login_time = datetime.fromisoformat(last_login)
                age = timezone.now() - last_login_time
                if age.total_seconds() > (self.require_fresh_login_minutes * 60):
                    request.session['next_after_fresh_login'] = request.get_full_path()
                    return redirect('accounts:fresh_login')
            else:
                request.session['next_after_fresh_login'] = request.get_full_path()
                return redirect('accounts:fresh_login')
        
        # Apply rate limiting
        if self.rate_limit_requests and request.user.is_authenticated:
            cache_key = f"rate_limit_user_{request.user.id}_{self.__class__.__name__}"
            current_count = cache.get(cache_key, 0)
            
            if current_count >= self.rate_limit_requests:
                messages.error(request, "Too many requests. Please try again later.")
                return redirect('accounts:dashboard')
            
            cache.set(cache_key, current_count + 1, self.rate_limit_window_minutes * 60)
        
        # Execute the view
        response = super().dispatch(request, *args, **kwargs)
        
        # Track activity
        if self.track_activity and request.user.is_authenticated:
            try:
                object_id = kwargs.get('pk') or kwargs.get('id')
                log_user_action(
                    user=request.user,
                    action=self.activity_type,
                    object_type=self.__class__.__name__,
                    object_id=object_id,
                    request=request
                )
            except Exception as e:
                logger.error(f"Failed to track activity: {str(e)}")
        
        return response


# =============================================================================
# Convenience Decorators
# =============================================================================

# Method decorators for class-based views
require_active_account_method = method_decorator(require_active_account)
require_verified_email_method = method_decorator(require_verified_email)
require_complete_profile_method = method_decorator(require_complete_profile)
check_account_lockout_method = method_decorator(check_account_lockout)
require_secure_connection_method = method_decorator(require_secure_connection)

# Combined decorators
def secure_account_required(view_func):
    """
    Combined decorator for secure account access.
    """
    @require_active_account
    @require_verified_email
    @check_account_lockout
    @require_secure_connection
    @csrf_protect
    @never_cache
    @functools.wraps(view_func)
    def wrapper(*args, **kwargs):
        return view_func(*args, **kwargs)
    return wrapper


def sensitive_operation(max_age_minutes: int = 30, require_confirmation: bool = True):
    """
    Combined decorator for sensitive operations.
    """
    def decorator(view_func):
        @secure_account_required
        @require_fresh_login(max_age_minutes)
        @track_sensitive_action(view_func.__name__, require_confirmation)
        @rate_limit_user(max_requests=5, window_minutes=5)
        @functools.wraps(view_func)
        def wrapper(*args, **kwargs):
            return view_func(*args, **kwargs)
        return wrapper
    return decorator


# =============================================================================
# Export all decorators
# =============================================================================

__all__ = [
    # Account Status Decorators
    'require_active_account',
    'require_verified_email',
    'require_complete_profile',
    'check_account_lockout',
    'require_password_change',
    
    # Activity Tracking Decorators
    'track_user_activity',
    'track_sensitive_action',
    
    # Rate Limiting Decorators
    'rate_limit_user',
    'rate_limit_ip',
    
    # Security Decorators
    'require_secure_connection',
    'require_fresh_login',
    
    # Class-based View Support
    'AccountsDecoratorMixin',
    
    # Method Decorators
    'require_active_account_method',
    'require_verified_email_method',
    'require_complete_profile_method',
    'check_account_lockout_method',
    'require_secure_connection_method',
    
    # Combined Decorators
    'secure_account_required',
    'sensitive_operation',
]