import uuid
from datetime import datetime, timezone

from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession

from src.core.exceptions import ConflictException, ForbiddenException, NotFoundException, ValidationException
from src.core.logging import get_logger
from src.domain.entities import TechnicalUserEntity, UserRole
from src.infrastructure.database.models import TechnicalUserModel
from src.infrastructure.database.repositories import TechnicalUserRepository
from src.services.auth.security import RBACGuard, TokenData, hash_password, verify_password

logger = get_logger("user_service")


class UserService:
    def __init__(self, session: AsyncSession) -> None:
        self._session = session
        self._repo = TechnicalUserRepository(session)

    async def get_user(self, user_id: uuid.UUID) -> TechnicalUserEntity:
        user = await self._repo.get_by_id(user_id)
        if not user:
            raise NotFoundException("User", str(user_id))
        return user

    async def list_users(self, *, role: str | None = None, is_active: bool | None = None, offset: int = 0, limit: int = 50, caller: TokenData | None = None) -> tuple[list[TechnicalUserEntity], int]:
        if caller and not RBACGuard.has_scope(caller, "users:read"):
            raise ForbiddenException("Scope 'users:read' required")
        return await self._repo.list_users(role=role, is_active=is_active, offset=offset, limit=limit)

    async def create_user(self, username: str, password: str, email: str | None = None, full_name: str | None = None, role: UserRole = UserRole.VIEWER, caller: TokenData | None = None) -> TechnicalUserEntity:
        if caller:
            RBACGuard.require_role(caller, UserRole.ADMIN)
        existing = await self._repo.get_by_username(username)
        if existing:
            raise ConflictException(f"Username '{username}' already exists")
        pw_hash = hash_password(password)
        entity = TechnicalUserEntity(username=username, email=email, full_name=full_name, role=role, is_active=True, scopes=[])
        user = await self._repo.create(entity, pw_hash)
        logger.info("user_created", extra={"username": username, "role": role.value, "created_by": caller.subject if caller else "system"})
        return user

    async def update_user(self, user_id: uuid.UUID, *, email: str | None = None, full_name: str | None = None, role: UserRole | None = None, is_active: bool | None = None, caller: TokenData | None = None) -> TechnicalUserEntity:
        if caller:
            RBACGuard.require_role(caller, UserRole.ADMIN)
        user = await self.get_user(user_id)
        if email is not None:
            user.email = email
        if full_name is not None:
            user.full_name = full_name
        if role is not None:
            user.role = role
        if is_active is not None:
            user.is_active = is_active
        user = await self._repo.update(user)
        logger.info("user_updated", extra={"user_id": str(user_id), "updated_by": caller.subject if caller else "system"})
        return user

    async def change_password(self, user_id: uuid.UUID, current_password: str, new_password: str, caller: TokenData | None = None) -> bool:
        stmt = select(TechnicalUserModel.password_hash).where(TechnicalUserModel.id == user_id)
        result = await self._session.execute(stmt)
        pw_hash = result.scalar_one_or_none()
        if not pw_hash or not verify_password(current_password, pw_hash):
            raise ValidationException("Current password is incorrect")
        new_hash = hash_password(new_password)
        stmt = update(TechnicalUserModel).where(TechnicalUserModel.id == user_id).values(password_hash=new_hash)
        await self._session.execute(stmt)
        logger.info("password_changed", extra={"user_id": str(user_id)})
        return True
