import hashlib
import secrets

from fastapi import Depends, HTTPException, Request, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer

from app.config import get_settings

_bearer = HTTPBearer(auto_error=False)


def generate_server_key() -> str:
    """Plaintext-ключ агента. Показывается один раз при создании сервера."""
    return "ghm_" + secrets.token_urlsafe(24)


def generate_mcp_token() -> str:
    """Plaintext персонального MCP-ключа (канон handbook mcp.md, префикс mcp_).
    Показывается один раз на странице «MCP-ключи»; в БД — хэш hash_key()."""
    return "mcp_" + secrets.token_urlsafe(24)


def mcp_token_hint(token: str) -> str:
    """Хвост для опознания ключа в списке (plaintext никогда не возвращается)."""
    return token[-8:]


def hash_key(key: str) -> str:
    return hashlib.sha256(key.encode()).hexdigest()


def auth_mode_error() -> HTTPException:
    """401/403, когда браузер не залогинен в gnexus-auth (GHARD_AUTH_CLIENT_ID задан)."""
    return HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="not authenticated")


async def require_admin(
    request: Request,
    creds: HTTPAuthorizationCredentials | None = Depends(_bearer),
) -> None:
    """Доступ к API панели (данные, CRUD, MCP).

    Порядок:
    1. Bearer GHARD_ADMIN_TOKEN (если задан) — скрипты, MCP, curl.
    2. Агенты: заголовок X-Server-Key проверяется только в ingest-роутере.
    3. GHARD_AUTH_CLIENT_ID задан → браузер с валидной сессионной cookie.
    4. auth выключен и admin_token пуст → API открыт (личная панель).
    """
    settings = get_settings()
    token = settings.admin_token
    if token and creds is not None and creds.scheme.lower() == "bearer" and creds.credentials == token:
        return
    if settings.auth_client_id:
        from app.auth import SESSION_COOKIE, get_session

        session = await get_session(request.cookies.get(SESSION_COOKIE))
        if session is None:
            raise auth_mode_error()
        return
    if not token:
        return
    raise HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="invalid admin token",
    )


def _mcp_401() -> HTTPException:
    # Причина (отозван / заблокирован / неверный) не раскрывается — канон mcp.md
    return HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="invalid MCP token",
        headers={"www-authenticate": "Bearer"},
    )


async def require_mcp(
    request: Request,
    creds: HTTPAuthorizationCredentials | None = Depends(_bearer),
) -> None:
    """Гард MCP (/mcp): персональный mcp_* ключ | статический админ-токен | cookie.

    Порядок:
    1. Bearer mcp_… — hash-lookup в mcp_tokens: отозванный или ключ владельца
       с users.blocked → тот же 401 (канон mcp.md); валидный → last_used_at.
    2. Bearer GHARD_ADMIN_TOKEN — статический супер-токен (совместимость).
    3. Cookie-сессия gnexus-auth — как в require_admin.
    4. auth выключен и admin_token пуст → открытый режим.

    Это dependency, а не ASGI-гард как в Synapse: /mcp здесь один APIRoute,
    dependency решает то же самое без middleware-обёртки.
    """
    settings = get_settings()
    bearer = creds.credentials if creds is not None and creds.scheme.lower() == "bearer" else ""
    if bearer.startswith("mcp_"):
        from app.auth import _now, get_user_row
        from app.db import get_db

        db = get_db()
        cursor = await db.execute(
            "SELECT id, user_id, revoked_at FROM mcp_tokens WHERE token_hash = ?",
            (hash_key(bearer),),
        )
        row = await cursor.fetchone()
        if row is None or row["revoked_at"]:
            raise _mcp_401()
        user = await get_user_row(row["user_id"])
        if user and user["blocked"]:
            raise _mcp_401()
        await db.execute(
            "UPDATE mcp_tokens SET last_used_at = ? WHERE id = ?",
            (_now().isoformat(), row["id"]),
        )
        await db.commit()
        request.state.mcp_token = dict(row)
        return
    if settings.admin_token and bearer and bearer == settings.admin_token:
        request.state.mcp_token = None
        return
    if settings.auth_client_id:
        from app.auth import SESSION_COOKIE, get_session

        session = await get_session(request.cookies.get(SESSION_COOKIE))
        if session is None:
            raise _mcp_401()
        request.state.mcp_token = None
        return
    if not settings.admin_token:
        request.state.mcp_token = None
        return
    raise _mcp_401()