"""
认证中间件

提供全局认证中间件和权限检查。
"""

import logging
import time
from typing import Optional, List, Set
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response, JSONResponse
from starlette.types import ASGIApp

from src.auth.jwt import verify_token
from src.auth.models import UserAuth, TokenPayload

logger = logging.getLogger(__name__)


class AuthMiddleware(BaseHTTPMiddleware):
    """
    全局认证中间件

    为所有请求添加认证信息，可选择性地保护路由。
    """

    # 不需要认证的路径
    PUBLIC_PATHS: Set[str] = {
        "/docs",
        "/redoc",
        "/openapi.json",
        "/api/v1/auth/login",
        "/api/v1/auth/register",
        "/api/v1/auth/refresh",
        "/health",
        "/metrics",
        "/api/v1/health",
    }

    # 路径前缀（以这些前缀开头的路径不需要认证）
    PUBLIC_PREFIXES: List[str] = [
        "/static/",
        "/api/v1/public/",
    ]

    def __init__(
        self,
        app: ASGIApp,
        require_auth: bool = False,
        public_paths: Optional[Set[str]] = None,
        public_prefixes: Optional[List[str]] = None
    ):
        """
        初始化认证中间件

        Args:
            app: ASGI 应用
            require_auth: 是否默认要求认证
            public_paths: 额外的公开路径
            public_prefixes: 额外的公开路径前缀
        """
        super().__init__(app)
        self.require_auth = require_auth

        if public_paths:
            self.PUBLIC_PATHS.update(public_paths)
        if public_prefixes:
            self.PUBLIC_PREFIXES.extend(public_prefixes)

    def _is_public_path(self, path: str) -> bool:
        """检查路径是否公开"""
        # 精确匹配
        if path in self.PUBLIC_PATHS:
            return True

        # 前缀匹配
        for prefix in self.PUBLIC_PREFIXES:
            if path.startswith(prefix):
                return True

        return False

    def _extract_token(self, request: Request) -> Optional[str]:
        """从请求中提取 token"""
        # 从 Authorization header
        auth_header = request.headers.get("Authorization", "")
        if auth_header.startswith("Bearer "):
            return auth_header[7:]

        # 从 query parameter (用于特殊场景，如直接链接分享)
        token = request.query_params.get("token")
        if token:
            return token

        # 从 cookie
        token = request.cookies.get("access_token")
        if token:
            return token

        return None

    async def dispatch(self, request: Request, call_next) -> Response:
        """
        处理请求

        Args:
            request: 请求对象
            call_next: 下一个中间件/处理函数

        Returns:
            响应对象
        """
        path = request.url.path

        # 提取 token
        token = self._extract_token(request)

        # 如果有 token，尝试验证
        user: Optional[UserAuth] = None
        if token:
            payload = await verify_token(token, token_type="access")
            if payload:
                user = UserAuth(
                    id=payload.sub,
                    username=f"user_{payload.sub[:8]}",
                    is_active=True,
                    permissions=payload.scopes or []
                )

        # 将用户信息附加到请求状态
        request.state.user = user
        request.state.is_authenticated = user is not None

        # 检查是否需要认证
        if self._is_public_path(path):
            # 公开路径，允许访问
            return await call_next(request)

        # 非公开路径
        if self.require_auth and not user:
            return JSONResponse(
                status_code=401,
                content={
                    "code": "4010",
                    "message": "Authentication required",
                    "data": None
                }
            )

        # 继续处理
        return await call_next(request)


class PermissionMiddleware(BaseHTTPMiddleware):
    """
    权限检查中间件

    基于路径进行权限检查。
    """

    def __init__(
        self,
        app: ASGIApp,
        path_permissions: Optional[dict] = None
    ):
        """
        初始化权限中间件

        Args:
            app: ASGI 应用
            path_permissions: 路径权限映射 {"/path": ["permission1", "permission2"]}
        """
        super().__init__(app)
        self.path_permissions = path_permissions or {}

    async def dispatch(self, request: Request, call_next) -> Response:
        """处理请求"""
        path = request.url.path
        user: Optional[UserAuth] = getattr(request.state, "user", None)

        # 检查路径权限
        for path_pattern, required_perms in self.path_permissions.items():
            if path.startswith(path_pattern):
                if not user:
                    return JSONResponse(
                        status_code=401,
                        content={
                            "code": "4010",
                            "message": "Authentication required",
                            "data": None
                        }
                    )

                if not user.is_superuser:
                    user_perms = set(user.permissions)
                    required = set(required_perms)

                    if not required.issubset(user_perms):
                        return JSONResponse(
                            status_code=403,
                            content={
                                "code": "4030",
                                "message": "Permission denied",
                                "data": {"required": list(required)}
                            }
                        )

                break

        return await call_next(request)


class RateLimitMiddleware(BaseHTTPMiddleware):
    """
    基于 Redis 的速率限制中间件

    为认证用户和匿名用户分别设置速率限制。
    使用 Redis 实现分布式速率限制。
    """

    def __init__(
        self,
        app: ASGIApp,
        authenticated_limit: int = 1000,  # per minute
        anonymous_limit: int = 60,  # per minute
        window_size: int = 60,  # 1 minute window
    ):
        super().__init__(app)
        self.authenticated_limit = authenticated_limit
        self.anonymous_limit = anonymous_limit
        self.window_size = window_size
        self._redis_client = None
        self._enabled = False

    async def _get_redis(self):
        """Lazy initialize Redis connection"""
        if not self._enabled:
            return None
        if self._redis_client is None:
            try:
                import redis.asyncio as aioredis
                from src.config.settings import REDIS_URL
                self._redis_client = await aioredis.from_url(
                    REDIS_URL,
                    encoding="utf-8",
                    decode_responses=True
                )
            except Exception as e:
                logger.warning(f"Redis not available for rate limiting: {e}")
                return None
        return self._redis_client

    async def _check_rate_limit(self, key: str, limit: int) -> tuple[bool, int, int]:
        """
        检查速率限制

        Returns:
            (allowed, remaining, reset_after)
        """
        redis = await self._get_redis()
        if not redis:
            # Redis unavailable, allow request
            return True, limit, 0

        try:
            current_time = int(time.time())
            window_start = current_time - self.window_size

            # Remove old entries
            await redis.zremrangebyscore(key, 0, window_start)

            # Count current requests in window
            current_count = await redis.zcard(key)

            if current_count >= limit:
                # Rate limit exceeded
                oldest = await redis.zrange(key, 0, 0, withscores=True)
                reset_after = int(oldest[0][1]) + self.window_size - current_time
                return False, 0, max(reset_after, 1)

            # Add current request
            await redis.zadd(key, {str(current_time): current_time})
            await redis.expire(key, self.window_size)

            remaining = limit - current_count - 1
            return True, remaining, 0

        except Exception as e:
            logger.error(f"Rate limit check failed: {e}")
            # Fail open - allow request if Redis fails
            return True, limit, 0

    async def dispatch(self, request: Request, call_next) -> Response:
        """处理请求"""
        # Check if Redis is enabled
        if not self._enabled:
            from src.config.settings import REDIS_ENABLED
            self._enabled = REDIS_ENABLED

        # Get user identifier
        user: Optional[UserAuth] = getattr(request.state, "user", None)
        if user:
            client_id = f"ratelimit:auth:{user.id}"
            limit = self.authenticated_limit
        else:
            # Use IP address for anonymous users
            client_ip = request.headers.get("X-Forwarded-For", request.client.host)
            client_id = f"ratelimit:anon:{client_ip}"
            limit = self.anonymous_limit

        # Check rate limit
        allowed, remaining, reset_after = await self._check_rate_limit(client_id, limit)

        # Process request
        response = await call_next(request)

        # Add rate limit headers
        if self._enabled:
            response.headers["X-RateLimit-Limit"] = str(limit)
            response.headers["X-RateLimit-Remaining"] = str(remaining)
            if reset_after > 0:
                response.headers["X-RateLimit-Reset-After"] = str(reset_after)

        if not allowed:
            return JSONResponse(
                status_code=429,
                content={
                    "code": "4290",
                    "message": "Rate limit exceeded",
                    "details": {
                        "limit": limit,
                        "reset_after": reset_after
                    }
                },
                headers={
                    "Retry-After": str(reset_after),
                    "X-RateLimit-Limit": str(limit),
                    "X-RateLimit-Remaining": "0"
                }
            )

        return response
