"""Async SQLAlchemy backend implementation."""

from __future__ import annotations

from typing import Any, Generic

from sqlalchemy import select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker

from aura_auth._core.exceptions import UserAlreadyExistsError, UserNotFoundError
from aura_auth._core.protocols import UserProtocol
from aura_auth._core.types import ID
from aura_auth.backend.base import BaseBackend


class SQLAlchemyBackend(BaseBackend[ID], Generic[ID]):
    """CRUD backend using SQLAlchemy async sessions."""

    def __init__(self, session_factory: async_sessionmaker[AsyncSession], user_model: type[Any]) -> None:
        self._session_factory = session_factory
        self._user_model = user_model

    async def get_by_id(self, user_id: ID) -> UserProtocol[ID] | None:
        async with self._session_factory() as session:
            result = await session.get(self._user_model, user_id)
            return result  # type: ignore[return-value]

    async def get_by_email(self, email: str) -> UserProtocol[ID] | None:
        async with self._session_factory() as session:
            stmt = select(self._user_model).where(self._user_model.email == email)  # type: ignore[arg-type]
            result = await session.execute(stmt)
            return result.scalar_one_or_none()  # type: ignore[return-value]

    async def create(self, data: dict[str, Any]) -> UserProtocol[ID]:
        user = self._user_model(**data)
        async with self._session_factory() as session:
            session.add(user)
            try:
                await session.commit()
            except IntegrityError as exc:
                await session.rollback()
                raise UserAlreadyExistsError("User with this email already exists") from exc
            await session.refresh(user)
            return user  # type: ignore[return-value]

    async def update(self, user: UserProtocol[ID], data: dict[str, Any]) -> UserProtocol[ID]:
        async with self._session_factory() as session:
            db_user = await session.get(self._user_model, user.id)
            if db_user is None:
                raise UserNotFoundError("User not found")
            for key, value in data.items():
                if value is None:
                    continue
                setattr(db_user, key, value)
            await session.commit()
            await session.refresh(db_user)
            return db_user  # type: ignore[return-value]

    async def delete(self, user: UserProtocol[ID]) -> None:
        async with self._session_factory() as session:
            db_user = await session.get(self._user_model, user.id)
            if db_user is None:
                return
            await session.delete(db_user)
            await session.commit()
