"""Общие зависимости FastAPI: авторизация, актор действия и сессия БД."""

import hashlib
from typing import Annotated, cast

from fastapi import Depends, HTTPException, Request
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.actor import VIA_API, VIA_UI, Actor, actor_from_request, agent_actor, declares_user
from app.db import get_db
from app.models import McpToken, User

DbDep = Annotated[Session, Depends(get_db)]


def user_from_bearer(request: Request, db: Session) -> dict[str, str] | None:
    """Fallback-аутентификация API-клиентов без сессии (расширение, агенты):
    заголовок `Authorization: Bearer <MCP-токен>` → user_id владельца.

    Токен в БД хранится только как sha256-хэш (как в McpAuthMiddleware для /mcp).
    Рядом с user_id кладём актора (ТЗ 3.20): по токену действие считается агентским,
    пока клиент не объявил `X-Actor: user` — см. actor_of.
    """
    auth = request.headers.get("authorization", "")
    token = auth.removeprefix("Bearer ").strip()
    if not token:
        return None
    token_hash = hashlib.sha256(token.encode()).hexdigest()
    # OUTER: строки users может не быть (тесты, «бесхозные» пользователи) — тогда
    # подпись владельца пуста, но токен обязан работать.
    row = db.execute(
        select(McpToken.id, McpToken.label, McpToken.user_id, User.name)
        .select_from(McpToken)
        .outerjoin(User, User.id == McpToken.user_id)
        .where(McpToken.token_hash == token_hash)
    ).first()
    if row is None:
        return None
    request.state.gntodo_actor = agent_actor(row.label or "", row.id, row.name or "", VIA_API)
    # Минимальный пользователь: API/сервисы читают только user_id (ТЗ 1.2).
    return {"user_id": row.user_id}


def require_user(request: Request, db: DbDep) -> dict[str, str]:
    """Защита API: сессия SSO или Bearer MCP-токен (расширение, ИИ-агенты)."""
    user = request.session.get("user")
    if user:
        # Сессия SSO — руки владельца (ТЗ 3.20)
        request.state.gntodo_actor = Actor(name=str(user.get("name") or ""), via=VIA_UI)
        return cast(dict[str, str], user)
    bearer_user = user_from_bearer(request, db)
    if not bearer_user:
        raise HTTPException(status_code=401, detail="Not authenticated")
    return bearer_user


UserDep = Annotated[dict[str, str], Depends(require_user)]


def actor_of(request: Request) -> Actor:
    """Актор действия этого запроса (ТЗ 3.20).

    Заголовок `X-Actor: user` (REST) объявляет действие владельца: актор станет
    `user` с его именем, но канал (`api`) сохранится — в журнале видно, что
    действие заявлено владельцем и пришло от клиента с токеном.
    """
    actor = actor_from_request(request)
    if actor.is_agent and declares_user(request.headers):
        return actor.declaring_user()
    return actor


def require_actor(request: Request, _user: UserDep) -> Actor:
    """Актор запроса — после require_user: тот уже заполнил request.state."""
    return actor_of(request)


ActorDep = Annotated[Actor, Depends(require_actor)]


def require_user_id(user: UserDep) -> str:
    """user_id текущего пользователя — ключ изоляции всех данных (ТЗ 1.2).

    Один слой чтения: require_user уже проверил 401.
    """
    return str(user["user_id"])


UserIdDep = Annotated[str, Depends(require_user_id)]
