"""FastAPI dependency injections for authentication and authorization.

These dependencies read the ``current_user`` payload that
:class:`AuthMiddleware` stores on ``request.state``.

- :func:`get_current_user` — returns the user payload or ``None`` (dev mode).
- :func:`require_authenticated` — 401 if not authenticated.
- :func:`require_permission` — 403 if the user's role lacks the permission.
- :func:`require_terminal_authorized` — 403 if the user is not authorized
  to use the local terminal (checks both the role permission and the
  per-user ``is_terminal_authorized`` flag from the DB).
"""

from __future__ import annotations

import logging
from typing import Callable

import aiosqlite
from fastapi import HTTPException, Request

from agentkit.server.auth.models import DEFAULT_AUTH_DB_PATH
from agentkit.server.auth.permissions import Permission, has_permission, is_dev_mode

logger = logging.getLogger(__name__)


async def get_current_user(request: Request) -> dict[str, object] | None:
    """Return the current user payload, or ``None`` in dev mode.

    The payload is set by :class:`AuthMiddleware` and contains
    ``user_id``, ``username``, and ``role``. When no auth middleware is
    active (e.g. dev mode with no keys configured), returns ``None``.

    U10 back-compat: the payload may or may not carry a ``sid`` field.
    Legacy V1 tokens (issued before the auth_sessions table existed)
    do not have ``sid``; the caller must check ``"sid" in payload`` and
    fall back to the V1 ``user_sessions`` table when the claim is
    missing.  This helper does NOT itself consult the session table —
    that responsibility belongs to the routes / dependencies that
    need a real session (e.g. :func:`get_current_session` in U4).
    """
    return getattr(request.state, "current_user", None)


async def require_authenticated(request: Request) -> dict[str, object]:
    """Require an authenticated user. Raises 401 if not authenticated.

    Use this as a FastAPI dependency for endpoints that need *any*
    logged-in user but do not require a specific role.
    """
    user = await get_current_user(request)
    if user is None:
        raise HTTPException(
            status_code=401,
            detail="Authentication required",
        )
    # U10 debug signal: log once per request when a legacy (no-sid)
    # token is in use.  This makes the migration window observable in
    # the server log; when the count drops to zero, we can remove the
    # legacy code path.
    if "sid" not in user:
        logger.debug(
            "Legacy JWT (no sid) in use for user_id=%s — accepted via U10 back-compat path",
            user.get("user_id"),
        )
    return user


def require_permission(*permissions: Permission) -> Callable[..., object]:
    """Build a FastAPI dependency that requires the given permissions.

    The user must have *all* of the given permissions (AND semantics).
    For OR semantics, call the dependency multiple times or use
    :func:`require_any_permission`.

    Usage::

        from agentkit.server.auth.dependencies import require_permission
        from agentkit.server.auth.permissions import Permission

        @router.post("/users", dependencies=[Depends(require_permission(Permission.USER_MANAGE))])
        async def create_user(...): ...

    Dev mode policy:
        - If the user is ``None`` (dev mode) AND none of the required
          permissions are "high-risk" (terminal / user / system), the
          request is allowed with a synthetic dev-mode user.
        - High-risk permissions always require authentication, even in
          dev mode.

    Args:
        permissions: One or more permissions the user must have.

    Returns:
        A FastAPI dependency function.
    """

    async def _dependency(request: Request) -> dict[str, object]:
        user = await get_current_user(request)

        # Dev mode: no authenticated user
        if is_dev_mode(user):
            high_risk = any(
                p
                in {
                    Permission.TERMINAL_LOCAL_USE,
                    Permission.TERMINAL_SERVER_USE,
                    Permission.TERMINAL_WHITELIST_MANAGE,
                    Permission.USER_MANAGE,
                    Permission.SYSTEM_CONFIG,
                }
                for p in permissions
            )
            if high_risk:
                raise HTTPException(
                    status_code=401,
                    detail="Authentication required for this endpoint",
                )
            # Synthetic dev-mode user with admin-like permissions for non-high-risk
            return {"user_id": None, "username": "dev", "role": "admin", "dev_mode": True}

        # Authenticated user — check each permission
        for perm in permissions:
            if not has_permission(user, perm):
                raise HTTPException(
                    status_code=403,
                    detail=f"Permission denied: requires {perm.value}",
                )
        return user

    return _dependency


def require_any_permission(*permissions: Permission) -> Callable[..., object]:
    """Build a FastAPI dependency that requires at least one of the permissions.

    Similar to :func:`require_permission` but uses OR semantics.
    """

    async def _dependency(request: Request) -> dict[str, object]:
        user = await get_current_user(request)

        if is_dev_mode(user):
            high_risk = any(
                p
                in {
                    Permission.TERMINAL_LOCAL_USE,
                    Permission.TERMINAL_SERVER_USE,
                    Permission.TERMINAL_WHITELIST_MANAGE,
                    Permission.USER_MANAGE,
                    Permission.SYSTEM_CONFIG,
                }
                for p in permissions
            )
            if high_risk:
                raise HTTPException(
                    status_code=401,
                    detail="Authentication required for this endpoint",
                )
            return {"user_id": None, "username": "dev", "role": "admin", "dev_mode": True}

        if not any(has_permission(user, p) for p in permissions):
            needed = " or ".join(p.value for p in permissions)
            raise HTTPException(
                status_code=403,
                detail=f"Permission denied: requires {needed}",
            )
        return user

    return _dependency


async def _resolve_db_path(request: Request):
    """Resolve the auth DB path from app.state or the default."""
    path = getattr(request.app.state, "auth_db_path", None)
    if path:
        from pathlib import Path

        return Path(path)
    return DEFAULT_AUTH_DB_PATH


async def require_terminal_authorized(request: Request) -> dict[str, object]:
    """Require a user authorized to use the local terminal.

    This checks both:
    1. The user's role has ``TERMINAL_LOCAL_USE`` permission.
    2. The user's ``is_terminal_authorized`` flag is True in the DB.

    The DB check ensures that an admin can revoke terminal access in
    real-time without waiting for the JWT to expire.
    """
    user = await require_permission(Permission.TERMINAL_LOCAL_USE)(request)

    if user.get("dev_mode"):
        return user

    user_id = user.get("user_id")
    if not user_id:
        raise HTTPException(status_code=401, detail="Authentication required")

    db_path = await _resolve_db_path(request)
    async with aiosqlite.connect(str(db_path)) as db:
        db.row_factory = aiosqlite.Row
        cursor = await db.execute(
            "SELECT is_terminal_authorized, is_active FROM users WHERE id = ?",
            (user_id,),
        )
        row = await cursor.fetchone()

    if row is None:
        raise HTTPException(status_code=404, detail="User not found")

    if not bool(row["is_active"]):
        raise HTTPException(status_code=403, detail="Account is disabled")

    if not bool(row["is_terminal_authorized"]):
        raise HTTPException(
            status_code=403,
            detail="Terminal access not authorized for this account",
        )

    return user


async def require_server_terminal_authorized(request: Request) -> dict[str, object]:
    """Require a user authorized to use the server terminal.

    Checks:
    1. The user's role has ``TERMINAL_SERVER_USE`` permission.
    2. The user's ``is_server_terminal_authorized`` flag is True in the DB.
    3. The user's ``is_active`` flag is True (account not disabled).
    """
    user = await require_permission(Permission.TERMINAL_SERVER_USE)(request)

    if user.get("dev_mode"):
        return user

    user_id = user.get("user_id")
    if not user_id:
        raise HTTPException(status_code=401, detail="Authentication required")

    db_path = await _resolve_db_path(request)
    async with aiosqlite.connect(str(db_path)) as db:
        db.row_factory = aiosqlite.Row
        cursor = await db.execute(
            "SELECT is_server_terminal_authorized, is_active FROM users WHERE id = ?",
            (user_id,),
        )
        row = await cursor.fetchone()

    if row is None:
        raise HTTPException(status_code=404, detail="User not found")

    if not bool(row["is_active"]):
        raise HTTPException(status_code=403, detail="Account is disabled")

    if not bool(row["is_server_terminal_authorized"]):
        raise HTTPException(
            status_code=403,
            detail="Server terminal access not authorized for this account",
        )

    return user
