from typing import Optional
from uuid import UUID
from datetime import datetime, timezone

from sqlalchemy import select, func, and_
from sqlalchemy.ext.asyncio import AsyncSession

from src.apps.auth.models.user import User
from src.core.exceptions import ConflictError, NotFoundError
from src.core.security import hash_password
from src.core.constants import UserRole, UserStatus


class UserService:
    def __init__(self, db: AsyncSession):
        self.db = db

    async def list_users(
        self,
        tenant_id: UUID,
        skip: int = 0,
        limit: int = 50,
        search: Optional[str] = None,
    ) -> tuple[list, int]:
        conditions = [User.tenant_id == tenant_id, User.deleted_at.is_(None)]
        if search:
            conditions.append(
                (User.email.ilike(f"%{search}%"))
                | (User.first_name.ilike(f"%{search}%"))
                | (User.last_name.ilike(f"%{search}%"))
            )
        count_q = await self.db.execute(select(func.count(User.id)).where(and_(*conditions)))
        total = count_q.scalar_one()
        result = await self.db.execute(
            select(User).where(and_(*conditions)).order_by(User.created_at.desc()).offset(skip).limit(limit)
        )
        return result.scalars().all(), total

    async def get_by_id(self, user_id: UUID, tenant_id: UUID) -> Optional[User]:
        result = await self.db.execute(
            select(User).where(
                and_(User.id == user_id, User.tenant_id == tenant_id, User.deleted_at.is_(None))
            )
        )
        return result.scalar_one_or_none()

    async def create_user(self, tenant_id: UUID, data: dict) -> User:
        # Check email uniqueness within tenant
        existing = await self.db.execute(
            select(User).where(
                and_(
                    User.email == data["email"].lower(),
                    User.tenant_id == tenant_id,
                    User.deleted_at.is_(None),
                )
            )
        )
        if existing.scalar_one_or_none():
            raise ConflictError("A user with this email already exists in this organization")

        user = User(
            tenant_id=tenant_id,
            email=data["email"].lower(),
            password_hash=hash_password(data["password"]),
            first_name=data["first_name"],
            last_name=data["last_name"],
            role=data.get("role", UserRole.STAFF.value),
            status=UserStatus.ACTIVE.value,
        )
        self.db.add(user)
        await self.db.flush()
        return user

    async def update_user(self, user_id: UUID, tenant_id: UUID, data: dict) -> User:
        user = await self.get_by_id(user_id, tenant_id)
        if not user:
            raise NotFoundError("User not found")
        for field, value in data.items():
            if value is not None and hasattr(user, field):
                setattr(user, field, value)
        await self.db.flush()
        return user

    async def deactivate_user(self, user_id: UUID, tenant_id: UUID):
        user = await self.get_by_id(user_id, tenant_id)
        if not user:
            raise NotFoundError("User not found")
        user.deleted_at = datetime.now(timezone.utc)
        await self.db.flush()
