from __future__ import annotations

import re

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

from app.db.models import User

EMAIL_PATTERN = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")


class UserServiceError(ValueError):
    """Raised for expected user-management validation failures."""


def normalize_email(value: str) -> str:
    return value.strip().lower()


def validate_email(value: str) -> str:
    email = normalize_email(value)
    if not EMAIL_PATTERN.fullmatch(email):
        raise UserServiceError("Enter a valid email address.")
    return email


def validate_password(value: str) -> str:
    password = value.strip()
    if len(password) < 8:
        raise UserServiceError("Password must be at least 8 characters.")
    return password


async def get_user_by_id(db_session: AsyncSession, user_id: int) -> User | None:
    result = await db_session.execute(select(User).where(User.id == user_id))
    return result.scalar_one_or_none()


async def get_user_by_email(db_session: AsyncSession, email: str) -> User | None:
    result = await db_session.execute(select(User).where(User.email == normalize_email(email)))
    return result.scalar_one_or_none()


async def list_users(db_session: AsyncSession) -> list[User]:
    result = await db_session.execute(select(User).order_by(User.email.asc()))
    return list(result.scalars().all())


async def create_user(
    db_session: AsyncSession,
    *,
    email: str,
    password_hash: str,
    is_admin: bool,
) -> User:
    user = User(
        email=validate_email(email),
        password_hash=password_hash,
        is_admin=is_admin,
        is_active=True,
    )
    db_session.add(user)
    try:
        await db_session.commit()
    except IntegrityError as exc:
        await db_session.rollback()
        raise UserServiceError("A user with that email already exists.") from exc
    await db_session.refresh(user)
    return user


async def set_user_active(
    db_session: AsyncSession,
    *,
    user: User,
    is_active: bool,
) -> User:
    user.is_active = is_active
    await db_session.commit()
    await db_session.refresh(user)
    return user


async def set_user_password_hash(
    db_session: AsyncSession,
    *,
    user: User,
    password_hash: str,
) -> User:
    user.password_hash = password_hash
    await db_session.commit()
    await db_session.refresh(user)
    return user
