"""
Caprail Auth Manager.

Session and token-based authentication manager.

Usage::

    # In a controller:
    success = await Auth.attempt(request, email="user@example.com", password="secret")
    user = Auth.user(request)
    Auth.logout(request)
    is_logged_in = Auth.check(request)
"""
from __future__ import annotations

import hashlib
import hmac
from typing import Any, TYPE_CHECKING

if TYPE_CHECKING:
    from caprail.http.request import Request


class Auth:
    """Static authentication helper."""

    _user_model: type | None = None
    _password_field: str = "password"

    @classmethod
    def configure(cls, user_model: type, password_field: str = "password") -> None:
        cls._user_model = user_model
        cls._password_field = password_field

    @classmethod
    async def attempt(cls, request: "Request", **credentials) -> bool:
        """
        Attempt to authenticate with the given credentials.

        Stores the user in the session on success.
        """
        if cls._user_model is None:
            raise RuntimeError("Auth not configured. Call Auth.configure(UserModel).")

        password = credentials.pop("password", None)
        query = cls._user_model.query()
        for field, value in credentials.items():
            query = query.where(field, value)
        user = await query.first()

        if user is None:
            return False

        stored = getattr(user, cls._password_field, None)
        if not stored or not cls._check_password(password or "", stored):
            return False

        request.session["_auth_id"] = getattr(user, user.primary_key)
        request.user = user
        return True

    @classmethod
    async def login(cls, request: "Request", user: Any) -> None:
        """Log in the given user directly (no credential check)."""
        request.session["_auth_id"] = getattr(user, user.primary_key)
        request.user = user

    @classmethod
    def logout(cls, request: "Request") -> None:
        request.session.pop("_auth_id", None)
        request.user = None

    @classmethod
    def check(cls, request: "Request") -> bool:
        return "_auth_id" in request.session or request.user is not None

    @classmethod
    def user(cls, request: "Request") -> Any | None:
        return request.user

    @classmethod
    def id(cls, request: "Request") -> Any | None:
        return request.session.get("_auth_id")

    # ------------------------------------------------------------------
    # Password hashing
    # ------------------------------------------------------------------

    @staticmethod
    def hash_password(password: str) -> str:
        import hashlib, os
        salt = os.urandom(16).hex()
        digest = hashlib.pbkdf2_hmac("sha256", password.encode(), salt.encode(), 260000).hex()
        return f"pbkdf2:sha256:260000:{salt}:{digest}"

    @staticmethod
    def _check_password(plain: str, hashed: str) -> bool:
        if hashed.startswith("pbkdf2:sha256:"):
            parts = hashed.split(":")
            _, _, iterations, salt, stored_digest = parts
            digest = hashlib.pbkdf2_hmac(
                "sha256", plain.encode(), salt.encode(), int(iterations)
            ).hex()
            return hmac.compare_digest(digest, stored_digest)
        # Fallback plain text (dev only)
        return plain == hashed
