"""Auth dependencies for Normbase API."""
from __future__ import annotations

from datetime import datetime, timezone
from typing import Annotated, Optional

from fastapi import Depends, Header, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from normbase.db.connection import get_db
from normbase.models.norm import APIKey
from security.auth.api_keys import verify_api_key_hash, hash_api_key
from security.auth.models import Principal
from security.auth.verifier import verify_internal_service_token


def _require_internal_token(
    authorization: Annotated[Optional[str], Header()] = None,
    x_service_token: Annotated[Optional[str], Header()] = None,
    x_agent_id: Annotated[Optional[str], Header()] = None,
    x_key_id: Annotated[Optional[str], Header()] = None,
    x_request_id: Annotated[Optional[str], Header()] = None,
    x_caller_id: Annotated[Optional[str], Header()] = None,
) -> Principal:
    principal = verify_internal_service_token(
        authorization=authorization,
        x_service_token=x_service_token,
    )
    if not principal.authenticated:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Unauthorized: invalid internal service token.",
        )
    principal.agent_id = x_agent_id
    principal.key_id = x_key_id
    principal.request_id = x_request_id
    principal.caller_id = x_caller_id
    return principal


def _to_utc(dt: datetime) -> datetime:
    if dt.tzinfo is None:
        return dt
    return dt.astimezone(timezone.utc).replace(tzinfo=None)


def _utc_now() -> datetime:
    return datetime.now(timezone.utc).replace(tzinfo=None)


def _extract_bearer(authorization: Optional[str]) -> str:
    if not authorization:
        return ""
    raw = authorization.strip()
    if raw.lower().startswith("bearer "):
        return raw[7:].strip()
    return raw


async def _require_runtime_auth(
    authorization: Annotated[Optional[str], Header()] = None,
    x_service_token: Annotated[Optional[str], Header()] = None,
    x_api_key: Annotated[Optional[str], Header()] = None,
    x_agent_id: Annotated[Optional[str], Header()] = None,
    x_key_id: Annotated[Optional[str], Header()] = None,
    x_request_id: Annotated[Optional[str], Header()] = None,
    x_caller_id: Annotated[Optional[str], Header()] = None,
    db: AsyncSession = Depends(get_db),
) -> Principal:
    # Compatibility: allow internal token callers.
    principal = verify_internal_service_token(
        authorization=authorization,
        x_service_token=x_service_token,
    )
    if principal.authenticated:
        principal.agent_id = x_agent_id
        principal.key_id = x_key_id
        principal.request_id = x_request_id
        principal.caller_id = x_caller_id
        return principal

    provided_key = (x_api_key or "").strip() or _extract_bearer(authorization)
    if not provided_key:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Unauthorized: missing runtime API key.",
        )

    hashed = hash_api_key(provided_key)
    row = (
        await db.execute(select(APIKey).where(APIKey.key_hash == hashed))
    ).scalar_one_or_none()
    if not row or not row.is_active:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Unauthorized: invalid runtime API key.",
        )
    if row.expires_at is not None and _to_utc(row.expires_at) <= _utc_now():
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Unauthorized: runtime API key expired.",
        )
    if not verify_api_key_hash(provided_key, row.key_hash):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Unauthorized: invalid runtime API key.",
        )

    return Principal(
        subject=str(row.id),
        auth_type="api-key",
        authenticated=True,
        agent_id=row.agent_id or x_agent_id,
        key_id=str(row.id),
        request_id=x_request_id,
        caller_id=x_caller_id,
    )


RequireInternalToken = Depends(_require_internal_token)
RequireRuntimeAuth = Depends(_require_runtime_auth)

