import redis.asyncio as aioredis
from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware
from .cache import get_redis, is_feature_enabled
from .auth import get_user_from_token
import json
import time

CONCURRENCY_PREFIX = "bff:concurrency:"
QUEUE_PREFIX = "bff:queue:"
CONFIG_PREFIX = "bff:concurrency:config:"


class ConcurrencyMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        path = request.url.path
        if path.startswith("/docs") or path.startswith("/redoc") or path.startswith("/openapi") or path == "/health":
            return await call_next(request)

        if not await is_feature_enabled("p4.concurrency_control"):
            return await call_next(request)

        authorization = request.headers.get("authorization")
        user = get_user_from_token(authorization)
        if not user:
            return await call_next(request)

        user_id = str(user.get("userId", ""))
        r = await get_redis()

        max_users = await _get_max_users(r)

        current_count = await r.get(f"{CONCURRENCY_PREFIX}count")
        current_count = int(current_count) if current_count else 0

        is_active = await r.exists(f"{CONCURRENCY_PREFIX}user:{user_id}")

        if is_active:
            await r.expire(f"{CONCURRENCY_PREFIX}user:{user_id}", 1800)
            return await call_next(request)

        if current_count < max_users:
            async with r.pipeline() as pipe:
                pipe.incr(f"{CONCURRENCY_PREFIX}count")
                pipe.setex(f"{CONCURRENCY_PREFIX}user:{user_id}", 1800, str(time.time()))
                await pipe.execute()
            return await call_next(request)

        queue_position = await r.llen(f"{QUEUE_PREFIX}waiting")
        await r.rpush(f"{QUEUE_PREFIX}waiting", json.dumps({
            "user_id": user_id,
            "queued_at": time.time()
        }))

        return Response(
            content=json.dumps({
                "error": "concurrency_limit",
                "message": f"当前在线用户已满，前方还有 {queue_position} 人等待",
                "queue_position": queue_position,
                "max_users": max_users
            }),
            status_code=429,
            media_type="application/json"
        )


async def _get_max_users(r: aioredis.Redis) -> int:
    cached = await r.get(f"{CONFIG_PREFIX}max_users")
    if cached:
        return int(cached)
    return 50


async def get_queue_status() -> dict:
    r = await get_redis()
    current_count = await r.get(f"{CONCURRENCY_PREFIX}count")
    current_count = int(current_count) if current_count else 0
    queue_length = await r.llen(f"{QUEUE_PREFIX}waiting")
    max_users = await _get_max_users(r)
    return {
        "current_users": current_count,
        "max_users": max_users,
        "queue_length": queue_length,
        "available_slots": max(0, max_users - current_count)
    }


async def release_user_session(user_id: str):
    r = await get_redis()
    existed = await r.exists(f"{CONCURRENCY_PREFIX}user:{user_id}")
    if existed:
        async with r.pipeline() as pipe:
            pipe.decr(f"{CONCURRENCY_PREFIX}count")
            pipe.delete(f"{CONCURRENCY_PREFIX}user:{user_id}")
            await pipe.execute()
        next_user = await r.lpop(f"{QUEUE_PREFIX}waiting")
        if next_user:
            data = json.loads(next_user)
            next_uid = str(data.get("user_id", ""))
            async with r.pipeline() as pipe:
                pipe.incr(f"{CONCURRENCY_PREFIX}count")
                pipe.setex(f"{CONCURRENCY_PREFIX}user:{next_uid}", 1800, str(time.time()))
                await pipe.execute()
