from typing import (
    Any,
    Dict,
    Optional,
    Union
)

from sqlalchemy.orm import Session
from sqlalchemy.future import select

from app.core.security import (
    get_password_hash,
    verify_password
)
from app.crud.base import CRUDBase
from app.models.user import User
from app.schemas.user import (
    UserCreate,
    UserUpdate
)


class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
    async def get_by_email(self, db: Session, *, email: str) -> Optional[User]:
        q = select(self.model).where(self.model.email == email)
        obj = await db.execute(q)
        return obj.scalar_one_or_none()

    def sync_get_by_email(self, db: Session, *, email: str) -> Optional[User]:
        q = select(self.model).where(self.model.email == email)
        obj = db.execute(q)
        return obj.scalar_one_or_none()

    async def create(self, db: Session, *, obj_in: UserCreate) -> User:
        db_obj = User(
            email=obj_in.email,
            hashed_password=get_password_hash(obj_in.password),
            username=obj_in.username,
            is_superuser=obj_in.is_superuser,
        )

        db.add(db_obj)
        await db.commit()
        await db.refresh(db_obj)

        return db_obj

    def sync_create(self, db: Session, *, obj_in: UserCreate) -> User:
        db_obj = User(
            email=obj_in.email,
            hashed_password=get_password_hash(obj_in.password),
            username=obj_in.username,
            is_superuser=obj_in.is_superuser,
        )

        db.add(db_obj)
        db.commit()
        db.refresh(db_obj)

        return db_obj

    async def update(
        self,
        db: Session,
        *,
        db_obj: User,
        obj_in: Union[UserUpdate, Dict[str, Any]]
    ) -> User:

        if isinstance(obj_in, dict):
            update_data = obj_in

        else:
            update_data = obj_in.model_dump(exclude_unset=True)

        if update_data.get("password"):
            hashed_password = get_password_hash(update_data["password"])
            del update_data["password"]
            update_data["hashed_password"] = hashed_password

        return await super().update(db, db_obj=db_obj, obj_in=update_data)

    async def authenticate(
        self, db: Session, *, email: str, password: str
    ) -> Optional[User]:
        user = await self.get_by_email(db, email=email)

        if user is None:
            return None

        if not verify_password(password, user.hashed_password):
            return None

        return user

    async def is_active(self, user: User) -> bool:
        return user.is_active

    async def is_superuser(self, user: User) -> bool:
        return user.is_superuser


user = CRUDUser(User)
