"""
IP 白黑名单访问控制中间件

支持三种访问模式（通过 settings.IP_ACCESS_MODE 切换）：

- ``production``  生产模式（默认）：放行所有 IP
- ``whitelist``   白名单模式：仅允许白名单内 IP 访问，其余返回 403
- ``blacklist``   黑名单模式：禁止黑名单内 IP 访问，其余放行

IP 来源优先级：X-Real-IP > X-Forwarded-For > REMOTE_ADDR

白名单/黑名单支持：
- 单 IP：``'1.2.3.4'``
- CIDR 网段：``'10.0.0.0/8'``
- IPv6：``'::1'`` / ``'2001:db8::/32'``

配置示例（settings.py）::

    IP_ACCESS_MODE = 'whitelist'          # production / whitelist / blacklist
    IP_WHITELIST = ['127.0.0.1', '10.0.0.0/8']
    IP_BLACKLIST = ['1.2.3.4']
    IP_FILTER_EXEMPT_PATHS = ['/admin/', '/health/']  # 免过滤路径
"""
import ipaddress
import logging

from django.http import JsonResponse
from django.conf import settings

logger = logging.getLogger('django')


def get_client_ip(request) -> str:
    """获取客户端真实 IP。

    优先级：X-Real-IP > X-Forwarded-For（取第一个非 unknown）> REMOTE_ADDR
    """
    # 1. X-Real-IP（Nginx 单机代理最可靠）
    x_real_ip = request.META.get('HTTP_X_REAL_IP')
    if x_real_ip:
        ip = x_real_ip.strip()
        if ip and ip.lower() != 'unknown':
            return ip

    # 2. X-Forwarded-For（代理链，第一个是客户端真实 IP）
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        for part in x_forwarded_for.split(','):
            part = part.strip()
            if part and part.lower() != 'unknown':
                return part

    # 3. REMOTE_ADDR 兜底
    return request.META.get('REMOTE_ADDR', '0.0.0.0') or '0.0.0.0'


def _parse_ip_list(raw_list) -> list:
    """将配置中的 IP/CIDR 字符串列表解析为 ipaddress 网络对象列表。"""
    networks = []
    for entry in raw_list or []:
        entry = str(entry).strip()
        if not entry:
            continue
        try:
            # 同时兼容单 IP 和 CIDR
            networks.append(ipaddress.ip_network(entry, strict=False))
        except ValueError:
            logger.warning(f"[IP过滤] 无法解析 IP/CIDR: {entry}，已忽略")
    return networks


def _ip_in_networks(ip_str: str, networks: list) -> bool:
    """判断 IP 是否落在任一网段内。"""
    try:
        ip_obj = ipaddress.ip_address(ip_str)
    except ValueError:
        # 非法 IP 地址，按不匹配处理
        return False
    return any(ip_obj in net for net in networks)


class IPFilterMiddleware:
    """IP 白黑名单访问控制中间件。

    根据 ``settings.IP_ACCESS_MODE`` 决定访问策略：

    ============  ====================================================
    模式          行为
    ============  ====================================================
    production   放行所有请求（默认）
    whitelist    仅白名单内 IP 可访问；其余 403
    blacklist    黑名单内 IP 禁止访问；其余放行
    ============  ====================================================
    """

    def __init__(self, get_response):
        self.get_response = get_response
        self.mode = getattr(settings, 'IP_ACCESS_MODE', 'production').lower()
        self.whitelist = _parse_ip_list(getattr(settings, 'IP_WHITELIST', []))
        self.blacklist = _parse_ip_list(getattr(settings, 'IP_BLACKLIST', []))
        self.exempt_paths = tuple(
            p.rstrip('/') for p in getattr(settings, 'IP_FILTER_EXEMPT_PATHS', [])
        )

        logger.info(
            f"[IP过滤] 中间件已启用，模式={self.mode}，"
            f"白名单={len(self.whitelist)}条，黑名单={len(self.blacklist)}条，"
            f"免过滤路径={self.exempt_paths}"
        )

    def __call__(self, request):
        # 生产模式直接放行
        if self.mode == 'production':
            return self.get_response(request)

        # 免过滤路径直接放行（如 /admin/、/health/）
        path = request.path
        if self.exempt_paths and path.startswith(self.exempt_paths):
            return self.get_response(request)

        client_ip = get_client_ip(request)

        # 白名单模式：不在白名单内 → 403
        if self.mode == 'whitelist':
            if not _ip_in_networks(client_ip, self.whitelist):
                logger.warning(
                    f"[IP过滤-白名单] 拒绝访问 path={path} ip={client_ip}"
                )
                return self._forbidden_response(client_ip)

        # 黑名单模式：在黑名单内 → 403
        elif self.mode == 'blacklist':
            if _ip_in_networks(client_ip, self.blacklist):
                logger.warning(
                    f"[IP过滤-黑名单] 拒绝访问 path={path} ip={client_ip}"
                )
                return self._forbidden_response(client_ip)

        return self.get_response(request)

    @staticmethod
    def _forbidden_response(client_ip: str) -> JsonResponse:
        """返回 403 JSON 响应。"""
        return JsonResponse(
            {'code': 403, 'message': '访问被拒绝'},
            status=403,
        )
