"""User manager orchestrating backend, password strategy, and tokens."""

from __future__ import annotations

import uuid
from collections.abc import Callable
from typing import Generic, cast

from aura_auth._core.exceptions import InvalidCredentialsError, InvalidTokenError, UserNotFoundError
from aura_auth._core.protocols import UserProtocol
from aura_auth._core.schemas import LoginRequest, UserCreate
from aura_auth._core.security import TokenHelper
from aura_auth._core.types import ID
from aura_auth.backend.base import BaseBackend
from aura_auth.strategies.password import PasswordStrategy


def default_parse_user_id(value: str) -> int | str | uuid.UUID:
    """Parse a JWT ``sub`` into a user id (UUID, int, or str)."""
    try:
        return uuid.UUID(value)
    except ValueError:
        pass
    if value.isdigit():
        return int(value)
    return value


class UserManager(Generic[ID]):
    """Orchestrates authentication across backend, strategy, and JWT helpers."""

    def __init__(
        self,
        backend: BaseBackend[ID],
        password_strategy: PasswordStrategy[ID],
        token_helper: TokenHelper,
        verify_token_helper: TokenHelper,
        parse_user_id: Callable[[str], ID] | None = None,
    ) -> None:
        self._backend = backend
        self._password_strategy = password_strategy
        self._token_helper = token_helper
        self._verify_token_helper = verify_token_helper
        self._parse_user_id: Callable[[str], ID] = cast(
            Callable[[str], ID],
            parse_user_id or default_parse_user_id,
        )

    async def register(self, data: UserCreate) -> UserProtocol[ID]:
        """Register a new user via the password strategy."""
        return await self._password_strategy.register(data, self._backend)

    async def login(self, credentials: LoginRequest) -> str:
        """Authenticate credentials and return an access token."""
        user = await self._password_strategy.authenticate(
            {"email": credentials.email, "password": credentials.password},
            self._backend,
        )
        if user is None:
            raise InvalidCredentialsError("Invalid email or password")
        await self._password_strategy.on_after_login(user)
        return self._token_helper.create_token(str(user.id))

    async def get_user(self, user_id: ID) -> UserProtocol[ID] | None:
        """Fetch a user by primary key."""
        return await self._backend.get_by_id(user_id)

    async def verify_token(self, token: str) -> UserProtocol[ID] | None:
        """Validate a JWT and return the associated user, if any."""
        try:
            payload = self._token_helper.decode_token(token)
        except InvalidTokenError:
            return None
        user_id = self._parse_user_id(str(payload["sub"]))
        return await self._backend.get_by_id(user_id)

    async def request_verify(self, user: UserProtocol[ID]) -> str:
        """Issue a short-lived token used to verify the user's email address."""
        return self._verify_token_helper.create_token(
            str(user.id),
            extra_claims={"token_type": "email_verify"},
        )

    async def verify(self, token: str) -> UserProtocol[ID]:
        """Mark a user as verified using a verification token."""
        try:
            payload = self._verify_token_helper.decode_token(token)
        except InvalidTokenError as exc:
            raise InvalidTokenError("Invalid verification token") from exc
        if payload.get("token_type") != "email_verify":
            raise InvalidTokenError("Invalid verification token")
        user_id = self._parse_user_id(str(payload["sub"]))
        user = await self._backend.get_by_id(user_id)
        if user is None:
            raise UserNotFoundError("User not found for verification token")
        updated = await self._backend.update(user, {"is_verified": True})
        return updated
