from typing import Optional

from bson.objectid import ObjectId
from fastapi import HTTPException, Request

from MongoDBConnection import users_collection
from model.access_token_model import AccessTokenModel
from model.staff_model import StaffModel


def get_bearer_token_from_request(request: Request) -> Optional[str]:
    auth_header = request.headers.get("Authorization", "")
    if not auth_header.lower().startswith("bearer "):
        return None

    raw_token = auth_header.split(" ", 1)[1].strip()
    return raw_token or None


def _find_user_by_id(actor_id: str, role: str) -> Optional[dict]:
    actor_id = str(actor_id or "").strip()
    if not actor_id:
        return None

    try:
        user = users_collection.find_one({"_id": ObjectId(actor_id), "role": role})
        if user:
            return user
    except Exception:
        pass

    user = users_collection.find_one({"id": actor_id, "role": role})
    if user:
        return user

    return users_collection.find_one({"userId": actor_id, "role": role})


def _build_actor_from_user(user: dict, role: str) -> dict:
    user_id = str(user.get("_id") or user.get("id") or user.get("userId") or "")
    name = user.get("name") or f"{user.get('firstName', '')} {user.get('lastName', '')}".strip()

    return {
        "id": user_id,
        "role": role,
        "permissions": user.get("permissions", []) if role == "staff" else [],
        "name": name or role,
        "email": user.get("email", ""),
        "status": user.get("status", "active" if role == "user" else "inactive"),
    }


async def get_current_actor_optional(request: Request):
    """
    Lấy actor hiện tại từ opaque access token.
    Không raise lỗi để middleware action log vẫn chạy được.
    """
    raw_token = get_bearer_token_from_request(request)
    if not raw_token:
        return None

    # TODO: Sau này không cần admin test nữa thì xóa toàn bộ block này.
    # Hiện tại block này cho phép dùng Authorization: Bearer 1 làm admin test,
    # kể cả khi chạy trên server. Chỉ nên giữ tạm trong giai đoạn test.
    if raw_token == "1":
        actor = {
            "id": "1",
            "role": "admin",
            "permissions": ["admin"],
            "name": "Admin Test",
            "email": "",
            "status": "active",
        }
        request.state.actor = actor
        request.state.raw_access_token = raw_token
        return actor

    token_doc = AccessTokenModel.find_valid_token(raw_token)
    if not token_doc:
        return None

    AccessTokenModel.touch_token(token_doc["_id"])

    actor_id = str(token_doc.get("actorId") or "").strip()
    actor_role = str(token_doc.get("actorRole") or "").strip().lower()

    if actor_role == "admin":
        actor = {
            "id": actor_id,
            "role": "admin",
            "permissions": ["admin"],
            "name": "Admin",
            "email": "admin@factory.com",
            "status": "active",
        }
    elif actor_role in {"staff", "user"}:
        user = _find_user_by_id(actor_id, actor_role)
        if not user:
            return None
        actor = _build_actor_from_user(user, actor_role)
    else:
        return None

    request.state.actor = actor
    request.state.raw_access_token = raw_token
    request.state.access_token_id = token_doc.get("_id")
    return actor


async def get_current_actor_required(request: Request):
    actor = await get_current_actor_optional(request)

    if not actor:
        raise HTTPException(status_code=401, detail="Unauthorized")

    if actor.get("role") != "admin" and actor.get("status") != "active":
        raise HTTPException(status_code=403, detail="Account is inactive")

    return actor


def require_user():
    async def dependency(request: Request):
        current_user = await get_current_actor_required(request)
        if current_user.get("role") != "user":
            raise HTTPException(status_code=403, detail="Forbidden: User account required")
        return current_user

    return dependency


def requires_permission(permission_name: str):
    async def dependency(request: Request):
        request.state.required_permission = permission_name
        current_user = await get_current_actor_required(request)

        # Admin full quyền.
        if current_user.get("role") == "admin":
            return current_user

        # Permission chỉ dành cho staff/admin, user thường không được vào admin API.
        if current_user.get("role") != "staff":
            raise HTTPException(status_code=403, detail="Forbidden: Staff account required")

        if StaffModel.check_access(current_user.get("permissions", []), permission_name):
            return current_user

        raise HTTPException(status_code=403, detail="Forbidden: No permission")

    return dependency


def requires_any_permission(permission_names: list[str]):
    async def dependency(request: Request):
        request.state.required_permission = ",".join(permission_names)
        current_user = await get_current_actor_required(request)

        if current_user.get("role") == "admin":
            return current_user

        if current_user.get("role") != "staff":
            raise HTTPException(status_code=403, detail="Forbidden: Staff account required")

        user_permissions = current_user.get("permissions", [])
        for permission_name in permission_names:
            if StaffModel.check_access(user_permissions, permission_name):
                return current_user

        raise HTTPException(status_code=403, detail="Forbidden: No permission")

    return dependency


def assert_actor_match(current_user: dict, client_actor_id: Optional[str]):
    expected_actor_id = str((current_user or {}).get("id") or "").strip()
    actual_actor_id = str(client_actor_id or "").strip()

    if not expected_actor_id:
        raise HTTPException(status_code=401, detail="Unauthorized")

    if not actual_actor_id:
        raise HTTPException(status_code=400, detail="actorId is required")

    if actual_actor_id != expected_actor_id:
        raise HTTPException(status_code=403, detail="Forbidden: actor mismatch")

    return expected_actor_id
