"""
Multi-Account Middleware
Extracts account from JWT token and injects into request context
"""
import logging
from django.utils.deprecation import MiddlewareMixin
from django.http import JsonResponse
from django.contrib.auth import logout
from rest_framework import status
import json
from datetime import datetime

logger = logging.getLogger('auth.middleware')

# Logout reason codes for precise tracking
LOGOUT_REASONS = {
    'SESSION_ACCOUNT_MISMATCH': 'Session contamination: account ID mismatch',
    'SESSION_USER_MISMATCH': 'Session contamination: user ID mismatch',
    'ACCOUNT_MISSING': 'Account not configured for this user',
    'ACCOUNT_SUSPENDED': 'Account is suspended',
    'ACCOUNT_CANCELLED': 'Account is cancelled',
    'PLAN_MISSING': 'No subscription plan assigned',
    'PLAN_INACTIVE': 'Subscription plan is inactive',
    'USER_INACTIVE': 'User account is inactive',
}

try:
    import jwt
    JWT_AVAILABLE = True
except ImportError:
    JWT_AVAILABLE = False

from django.conf import settings


class AccountContextMiddleware(MiddlewareMixin):
    """
    Middleware that extracts account information from JWT token
    and adds it to request context for account isolation.
    """
    
    def process_request(self, request):
        """Extract account from JWT token in Authorization header or session."""
        # Skip for admin and auth endpoints
        if request.path.startswith('/admin/') or request.path.startswith('/api/v1/auth/'):
            return None
        
        # First, try to get user from Django session (cookie-based auth)
        # This handles cases where frontend uses credentials: 'include' with session cookies
        if hasattr(request, 'user') and request.user and request.user.is_authenticated:
            # CRITICAL FIX: Never query DB again or mutate request.user
            # Django's AuthenticationMiddleware already loaded the user correctly
            # Just use it directly and set request.account from the ALREADY LOADED relationship
            try:
                # Validate account/plan - but use the user object already set by Django
                validation_error = self._validate_account_and_plan(request, request.user)
                if validation_error:
                    return validation_error
                
                # Set request.account from the user's account relationship
                # This is already loaded, no need to query DB again
                request.account = getattr(request.user, 'account', None)
                
                # REMOVED: Session contamination checks on every request
                # These were causing random logouts - session integrity handled by Django
                
                return None
            except (AttributeError, Exception):
                # If anything fails, just set account to None and continue
                request.account = None
                return None
        
        # Get token from Authorization header (JWT auth - for future implementation)
        auth_header = request.META.get('HTTP_AUTHORIZATION', '')
        if not auth_header.startswith('Bearer '):
            # No JWT token - if session auth didn't work, set account to None
            # But don't set request.user to None - it might be set by Django's auth middleware
            if not hasattr(request, 'account'):
                request.account = None
            return None
        
        token = auth_header.split(' ')[1] if len(auth_header.split(' ')) > 1 else None
        if not token:
            if not hasattr(request, 'account'):
                request.account = None
            return None
        
        try:
            if not JWT_AVAILABLE:
                # JWT library not installed yet - skip for now
                request.account = None
                return None
            
            # Decode JWT token with signature verification
            # Use JWT_SECRET_KEY from settings (falls back to SECRET_KEY if not set)
            jwt_secret = getattr(settings, 'JWT_SECRET_KEY', getattr(settings, 'SECRET_KEY', None))
            if not jwt_secret:
                raise ValueError("JWT_SECRET_KEY or SECRET_KEY must be set in settings")
            
            decoded = jwt.decode(token, jwt_secret, algorithms=[getattr(settings, 'JWT_ALGORITHM', 'HS256')])
            
            # Extract user and account info from token
            user_id = decoded.get('user_id')
            account_id = decoded.get('account_id')
            
            if user_id:
                from .models import User, Account
                try:
                    # Get user from DB (but don't set request.user - let DRF authentication handle that)
                    # Only set request.account for account context
                    user = User.objects.select_related('account', 'account__plan').get(id=user_id)
                    validation_error = self._validate_account_and_plan(request, user)
                    if validation_error:
                        return validation_error
                    if account_id:
                        # Verify account still exists
                        try:
                            account = Account.objects.get(id=account_id)
                            request.account = account
                        except Account.DoesNotExist:
                            # Account from token doesn't exist - don't fallback, set to None
                            request.account = None
                    else:
                        # No account_id in token - set to None (don't fallback to user.account)
                        request.account = None
                except (User.DoesNotExist, Account.DoesNotExist):
                    request.account = None
            else:
                request.account = None
                
        except jwt.InvalidTokenError:
            request.account = None
        except Exception:
            # Fail silently for now - allow unauthenticated access
            request.account = None
        
        return None
    
    def _validate_account_and_plan(self, request, user):
        """
        Ensure the authenticated user has an account and an active plan.
        Uses shared validation helper for consistency.
        """
        from .utils import validate_account_and_plan
        
        is_valid, error_message, http_status = validate_account_and_plan(user)
        
        if not is_valid:
            return self._deny_request(request, error_message, http_status)
        
        return None
    
    def _deny_request(self, request, error, status_code):
        """Logout session users (if any) and return a consistent JSON error with detailed tracking."""
        # Determine logout reason code based on error message
        reason_code = 'UNKNOWN'
        if 'Account not configured' in error or 'Account not found' in error:
            reason_code = 'ACCOUNT_MISSING'
        elif 'suspended' in error.lower():
            reason_code = 'ACCOUNT_SUSPENDED'
        elif 'cancelled' in error.lower():
            reason_code = 'ACCOUNT_CANCELLED'
        elif 'No subscription plan' in error or 'plan assigned' in error.lower():
            reason_code = 'PLAN_MISSING'
        elif 'plan is inactive' in error.lower() or 'Active subscription required' in error:
            reason_code = 'PLAN_INACTIVE'
        elif 'inactive' in error.lower():
            reason_code = 'USER_INACTIVE'
        
        try:
            if hasattr(request, 'user') and request.user and request.user.is_authenticated:
                logger.warning(
                    f"[AUTO-LOGOUT] {reason_code}: {error}. "
                    f"User={request.user.id}, Account={getattr(request, 'account', None)}, "
                    f"Path={request.path}, IP={request.META.get('REMOTE_ADDR')}, "
                    f"Status={status_code}, Timestamp={datetime.now().isoformat()}"
                )
                logout(request)
        except Exception as e:
            logger.error(f"[AUTO-LOGOUT] Error during logout: {e}")
        
        return JsonResponse(
            {
                'success': False,
                'error': error,
                'logout_reason': reason_code,
                'logout_message': LOGOUT_REASONS.get(reason_code, error),
                'logout_path': request.path,
                'logout_context': {
                    'user_id': request.user.id if hasattr(request, 'user') and request.user and request.user.is_authenticated else None,
                    'account_id': getattr(request, 'account', None).id if hasattr(request, 'account') and getattr(request, 'account', None) else None,
                    'status_code': status_code,
                }
            },
            status=status_code,
        )

