from __future__ import annotations

from datetime import UTC, datetime
from uuid import uuid4

from sqlalchemy import func, select

from app.core.database import session_scope
from app.modules.users.models import User, UserProfile

ALLOWED_ROLES = {"user", "admin"}
ALLOWED_STATUSES = {"pending", "active", "blocked"}


def _detach(db, instance):
    db.refresh(instance)
    db.expunge(instance)
    return instance


def create_user(
    email: str,
    password_hash: str,
    role: str = "user",
    is_superuser: bool = False,
    status: str = "pending",
) -> User:
    if role not in ALLOWED_ROLES:
        raise ValueError("INVALID_ROLE")
    if status not in ALLOWED_STATUSES:
        raise ValueError("INVALID_STATUS")
    if is_superuser and role != "admin":
        raise ValueError("SUPERUSER_REQUIRES_ADMIN")
    with session_scope() as db:
        user = User(
            id=str(uuid4()),
            email=email.lower(),
            password_hash=password_hash,
            role=role,
            is_superuser=is_superuser,
            status=status,
        )
        db.add(user)
        db.flush()
        profile = UserProfile(user_id=user.id, display_name=email.split("@")[0])
        db.add(profile)
        db.flush()
        return _detach(db, user)


def get_user_by_email(email: str) -> User | None:
    with session_scope() as db:
        user = db.scalar(select(User).where(User.email == email.lower()))
        if not user:
            return None
        return _detach(db, user)


def get_user_by_id(user_id: str) -> User | None:
    with session_scope() as db:
        user = db.get(User, user_id)
        if not user:
            return None
        return _detach(db, user)


def update_user(user: User) -> None:
    if user.role not in ALLOWED_ROLES:
        raise ValueError("INVALID_ROLE")
    if user.status not in ALLOWED_STATUSES:
        raise ValueError("INVALID_STATUS")
    if user.is_superuser and user.role != "admin":
        raise ValueError("SUPERUSER_REQUIRES_ADMIN")
    with session_scope() as db:
        db_user = db.get(User, user.id)
        if not db_user:
            return
        db_user.email = user.email
        db_user.password_hash = user.password_hash
        db_user.role = user.role
        db_user.is_superuser = user.is_superuser
        db_user.status = user.status
        db_user.failed_login_attempts = user.failed_login_attempts
        db_user.locked_until = user.locked_until
        db_user.email_verified_at = user.email_verified_at
        db_user.updated_at = datetime.now(UTC)


def list_users(page: int, limit: int) -> tuple[list[User], int]:
    with session_scope() as db:
        total = db.scalar(select(func.count()).select_from(User)) or 0
        users = db.scalars(
            select(User).order_by(User.created_at.desc()).offset((page - 1) * limit).limit(limit)
        ).all()
        return [_detach(db, user) for user in users], total


def count_users() -> int:
    with session_scope() as db:
        return db.scalar(select(func.count()).select_from(User)) or 0


def count_users_registered_today() -> int:
    today = datetime.now(UTC).date()
    with session_scope() as db:
        return (
            db.scalar(
                select(func.count())
                .select_from(User)
                .where(func.date(User.created_at) == today)
            )
            or 0
        )


def count_admins() -> int:
    with session_scope() as db:
        return db.scalar(select(func.count()).select_from(User).where(User.role == "admin")) or 0


def count_superusers() -> int:
    with session_scope() as db:
        return (
            db.scalar(
                select(func.count())
                .select_from(User)
                .where(User.role == "admin", User.is_superuser.is_(True))
            )
            or 0
        )


def delete_user(user_id: str) -> bool:
    with session_scope() as db:
        user = db.get(User, user_id)
        if not user:
            return False
        db.delete(user)
        return True


def get_profile(user_id: str) -> UserProfile:
    with session_scope() as db:
        profile = db.get(UserProfile, user_id)
        if not profile:
            raise KeyError(user_id)
        return _detach(db, profile)


def update_profile(
    user_id: str,
    display_name: str | None = None,
    avatar_url: str | None = None,
) -> UserProfile:
    with session_scope() as db:
        profile = db.get(UserProfile, user_id)
        if not profile:
            raise KeyError(user_id)
        if display_name is not None:
            profile.display_name = display_name
        if avatar_url is not None:
            profile.avatar_url = avatar_url
        db.flush()
        return _detach(db, profile)
