"""
Admin IP Restriction Middleware
Restricts Django admin access to specific IP addresses only
"""

from django.http import HttpResponseForbidden
from django.conf import settings
from django.urls import resolve
import ipaddress
import logging

logger = logging.getLogger('django.security')


class AdminIPRestrictionMiddleware:
    """
    Restricts Django admin access to whitelisted IP addresses only.
    This provides an additional layer of security for the admin panel.
    """
    
    def __init__(self, get_response):
        self.get_response = get_response
        
        # Get allowed admin IPs from settings
        # Default to the user's IP if not specified
        admin_ips = getattr(settings, 'ADMIN_ALLOWED_IPS', ['193.194.155.249'])
        
        # Convert to list if it's a string
        if isinstance(admin_ips, str):
            admin_ips = [ip.strip() for ip in admin_ips.split(',') if ip.strip()]
        
        self.allowed_ips = []
        for ip_str in admin_ips:
            try:
                # Support both single IPs and CIDR notation
                if '/' in ip_str:
                    self.allowed_ips.append(ipaddress.ip_network(ip_str, strict=False))
                else:
                    self.allowed_ips.append(ipaddress.ip_address(ip_str))
            except ValueError:
                logger.warning(f"Invalid IP address in ADMIN_ALLOWED_IPS: {ip_str}")
        
        # Also allow localhost for development
        self.allowed_ips.extend([
            ipaddress.ip_address('127.0.0.1'),
            ipaddress.ip_address('::1'),
        ])

    def get_client_ip(self, request):
        """
        Get the real client IP address, handling proxy headers
        """
        # Check for forwarded IP (from proxy/load balancer)
        x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
        if x_forwarded_for:
            # Get the first IP in the chain (original client)
            ip = x_forwarded_for.split(',')[0].strip()
            return ip
        
        # Check for real IP header (some proxies use this)
        x_real_ip = request.META.get('HTTP_X_REAL_IP')
        if x_real_ip:
            return x_real_ip.strip()
        
        # Fall back to REMOTE_ADDR
        return request.META.get('REMOTE_ADDR', '')

    def is_admin_path(self, request):
        """
        Check if the request is for the Django admin
        """
        path = request.path
        return path.startswith('/admin/')

    def is_ip_allowed(self, client_ip):
        """
        Check if the client IP is in the allowed list
        """
        try:
            client_ip_obj = ipaddress.ip_address(client_ip)
            
            # Check if IP matches any allowed IP or network
            for allowed in self.allowed_ips:
                if isinstance(allowed, ipaddress.IPv4Address) or isinstance(allowed, ipaddress.IPv6Address):
                    # Direct IP match
                    if client_ip_obj == allowed:
                        return True
                elif isinstance(allowed, ipaddress.IPv4Network) or isinstance(allowed, ipaddress.IPv6Network):
                    # Network/CIDR match
                    if client_ip_obj in allowed:
                        return True
            
            return False
        except ValueError:
            logger.error(f"Invalid IP address format: {client_ip}")
            return False

    def __call__(self, request):
        # Only check admin paths
        if not self.is_admin_path(request):
            return self.get_response(request)
        
        # In DEBUG mode, you might want to allow all (optional)
        # Uncomment the next 3 lines if you want to disable IP restriction in DEBUG mode
        # if settings.DEBUG:
        #     return self.get_response(request)
        
        # Get client IP
        client_ip = self.get_client_ip(request)
        
        if not client_ip:
            logger.warning("Could not determine client IP for admin access attempt")
            return HttpResponseForbidden(
                "<h1>Access Denied</h1>"
                "<p>Unable to verify your IP address. Admin access is restricted.</p>"
            )
        
        # Check if IP is allowed
        if not self.is_ip_allowed(client_ip):
            logger.warning(
                f"Blocked admin access attempt from IP: {client_ip} "
                f"to path: {request.path}"
            )
            return HttpResponseForbidden(
                "<h1>Access Denied</h1>"
                "<p>Admin access is restricted to authorized IP addresses only.</p>"
                f"<p>Your IP: {client_ip}</p>"
            )
        
        # IP is allowed, continue
        return self.get_response(request)

