"""
自定义中间件模块
"""
import io
import json
import logging

from django.conf import settings
from django.http import QueryDict
from django.http.multipartparser import MultiPartParserError
from django.utils.datastructures import MultiValueDict
from rest_framework_simplejwt.authentication import JWTAuthentication

logger = logging.getLogger(__name__)


class ApiAuditLogMiddleware:
    """
    API审计日志中间件
    
    用于记录特定URL前缀的POST/PUT/PATCH请求，保存"创建/更新"操作的历史现场。

    特性：
    - 只记录POST、PUT、PATCH请求
    - 通过URL前缀白名单过滤
    - 支持multipart/form-data请求，文件字段只记录元信息
    - 通过Celery异步写入，不阻塞API响应
    - 在AuthenticationMiddleware之后运行，可获取request.user
    
    配置项（settings.py）：
    - API_AUDIT_LOG_ENABLED: 是否启用（默认True）
    - API_AUDIT_LOG_URL_PREFIXES: URL前缀白名单列表
    """
    
    def __init__(self, get_response):
        self.get_response = get_response
        self.enabled = getattr(settings, 'API_AUDIT_LOG_ENABLED', True)
        self.url_prefixes = getattr(settings, 'API_AUDIT_LOG_URL_PREFIXES', [])
    
    def __call__(self, request):
        # 检查是否启用
        if not self.enabled:
            return self.get_response(request)
        
        # 只处理POST、PUT、PATCH请求
        if request.method not in ('POST', 'PUT', 'PATCH'):
            return self.get_response(request)
        
        # 检查URL是否在白名单中
        if not self._is_url_matched(request.path):
            logger.debug('ApiAuditLog: URL不匹配白名单, path=%s, prefixes=%s', request.path, self.url_prefixes)
            return self.get_response(request)
        
        logger.info('ApiAuditLog: 捕获到%s请求, path=%s', request.method, request.path)
        
        # 在请求进入视图前，缓存请求体数据
        # 注意：request.body只能读取一次，需要在这里缓存
        request_data = self._extract_request_data(request)
        query_params = dict(request.GET)
        
        # 执行视图
        response = self.get_response(request)
        
        # 获取用户信息（此时DRF的JWT认证已完成）
        user_id, username = self._get_user_info(request)
        
        logger.info('ApiAuditLog: 准备发送任务, user=%s, status=%s', username, response.status_code)
        
        # 异步保存审计日志
        self._save_audit_log(
            url=request.path,
            method=request.method,
            request_data=request_data,
            query_params=query_params,
            user_id=user_id,
            username=username,
            response_status=response.status_code,
        )
        
        return response
    
    def _is_url_matched(self, path: str) -> bool:
        """检查URL是否匹配白名单前缀"""
        for prefix in self.url_prefixes:
            if path.startswith(prefix):
                return True
        return False
    
    def _get_post_and_files(self, request):
        """
        获取表单字段和文件，兼容PUT/PATCH请求

        Django的request.POST/request.FILES只在method为POST时才会被解析，
        PUT/PATCH请求即使是multipart/form-data或urlencoded也不会自动填充，
        因此这里针对非POST方法手动解析。
        """
        if request.method == 'POST':
            return request.POST, request.FILES

        content_type = request.content_type or ''

        if 'multipart/form-data' in content_type:
            try:
                return request.parse_file_upload(request.META, io.BytesIO(request.body))
            except MultiPartParserError:
                return QueryDict(), MultiValueDict()
        elif 'application/x-www-form-urlencoded' in content_type:
            return QueryDict(request.body, encoding=request.encoding or settings.DEFAULT_CHARSET), MultiValueDict()

        return QueryDict(), MultiValueDict()

    def _extract_request_data(self, request) -> dict:
        """
        提取请求数据

        对于multipart/form-data请求，分别处理表单字段和文件字段。
        文件字段只记录元信息（文件名、大小、类型），不记录二进制内容。
        """
        content_type = request.content_type or ''

        if 'multipart/form-data' in content_type:
            # multipart请求：分别处理表单字段和文件
            data = {}
            post, files = self._get_post_and_files(request)

            # 处理普通表单字段
            for key, values in post.lists():
                if len(values) == 1:
                    data[key] = values[0]
                else:
                    data[key] = values

            # 处理文件字段：只记录元信息
            for key, file_list in files.lists():
                file_infos = []
                for f in file_list:
                    file_infos.append({
                        '_type': 'file',
                        'name': f.name,
                        'size': f.size,
                        'content_type': f.content_type,
                    })
                if len(file_infos) == 1:
                    data[key] = file_infos[0]
                else:
                    data[key] = file_infos

            return data

        elif 'application/json' in content_type:
            # JSON请求：解析body
            try:
                return json.loads(request.body.decode('utf-8'))
            except (json.JSONDecodeError, UnicodeDecodeError):
                return {'_raw': request.body.decode('utf-8', errors='replace')}

        elif 'application/x-www-form-urlencoded' in content_type:
            # 表单请求
            data = {}
            post, _ = self._get_post_and_files(request)
            for key, values in post.lists():
                if len(values) == 1:
                    data[key] = values[0]
                else:
                    data[key] = values
            return data

        else:
            # 其他类型：尝试作为文本记录
            try:
                body = request.body.decode('utf-8')
                if body:
                    return {'_raw': body}
                return {}
            except UnicodeDecodeError:
                return {'_raw': '<binary data>'}
    
    def _get_user_info(self, request) -> tuple[int | None, str]:
        """
        获取用户信息
        
        优先从request.user获取（Session认证或DRF已设置）。
        如果未认证，尝试手动进行JWT认证。
        """
        # 尝试从request.user获取
        if hasattr(request, 'user') and request.user.is_authenticated:
            return request.user.id, request.user.username
        
        # 尝试手动JWT认证
        try:
            auth = JWTAuthentication()
            user_auth_tuple = auth.authenticate(request)
            if user_auth_tuple:
                user = user_auth_tuple[0]
                return user.id, user.username
        except Exception:
            pass
        
        return None, ''
    
    def _save_audit_log(
        self,
        url: str,
        method: str,
        request_data: dict,
        query_params: dict,
        user_id: int | None,
        username: str,
        response_status: int | None,
    ):
        """通过Celery异步保存审计日志"""
        try:
            from api_v1.tasks import save_api_audit_log
            save_api_audit_log.delay(
                url=url,
                method=method,
                request_data=request_data,
                query_params=query_params,
                user_id=user_id,
                username=username,
                response_status=response_status,
            )
        except Exception as e:
            # 日志保存失败不应影响正常请求
            logger.error('发送API审计日志任务失败: %s', str(e), exc_info=True)
