from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from sqlalchemy.orm import selectinload
from sqlalchemy.exc import SQLAlchemyError, IntegrityError, OperationalError
from typing import Optional
import logging

from app.models import User as UserModel, UserGroup as UserGroupModel
from app.schemas.user import UserCreate
from app.core.security import hash_password
from app.core.exceptions import (
    UserCreationError,
    EmailAlreadyRegisteredError,
    DatabaseConnectionError,
    DatabaseIntegrityError,
    DatabaseQueryError,
    DatabaseTransactionError,
    UserOperationError
)

logger = logging.getLogger(__name__)

async def get_user_by_email(db: AsyncSession, email: str) -> Optional[UserModel]:
    """Fetches a user from the database by email, with common relationships."""
    try:
        stmt = (
            select(UserModel)
            .filter(UserModel.email == email)
            .options(
                selectinload(UserModel.group_associations).selectinload(UserGroupModel.group),
            )
        )
        result = await db.execute(stmt)
        return result.scalars().first()
    except OperationalError as e:
        logger.error(f"Database connection error while fetching user by email '{email}': {str(e)}", exc_info=True)
        raise DatabaseConnectionError(f"Failed to connect to database: {str(e)}")
    except SQLAlchemyError as e:
        logger.error(f"Unexpected SQLAlchemy error while fetching user by email '{email}': {str(e)}", exc_info=True)
        raise DatabaseQueryError(f"Failed to query user: {str(e)}")

async def create_user(db: AsyncSession, user_in: UserCreate, is_guest: bool = False) -> UserModel:
    """Creates a new user record in the database with common relationships loaded."""
    try:
        async with db.begin_nested() if db.in_transaction() else db.begin() as transaction:
            _hashed_password = hash_password(user_in.password)
            db_user = UserModel(
                email=user_in.email,
                hashed_password=_hashed_password,
                name=user_in.name,
                is_guest=is_guest
            )
            db.add(db_user)
            await db.flush()

            stmt = (
                select(UserModel)
                .where(UserModel.id == db_user.id)
                .options(
                    selectinload(UserModel.group_associations).selectinload(UserGroupModel.group),
                    selectinload(UserModel.created_groups)
                )
            )
            result = await db.execute(stmt)
            loaded_user = result.scalar_one_or_none()

            if loaded_user is None:
                raise UserOperationError("Failed to load user after creation.")
            
            return loaded_user
    except IntegrityError as e:
        logger.error(f"Database integrity error during user creation for email '{user_in.email}': {str(e)}", exc_info=True)
        if "unique constraint" in str(e).lower() and ("users_email_key" in str(e).lower() or "ix_users_email" in str(e).lower()):
            raise EmailAlreadyRegisteredError(email=user_in.email)
        raise DatabaseIntegrityError(f"Failed to create user due to integrity issue: {str(e)}")
    except OperationalError as e:
        logger.error(f"Database connection error during user creation for email '{user_in.email}': {str(e)}", exc_info=True)
        raise DatabaseConnectionError(f"Database connection error during user creation: {str(e)}")
    except SQLAlchemyError as e:
        logger.error(f"Unexpected SQLAlchemy error during user creation for email '{user_in.email}': {str(e)}", exc_info=True)
        raise DatabaseTransactionError(f"Failed to create user due to other DB error: {str(e)}")