from __future__ import annotations

import asyncio
import logging
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from typing import Any, AsyncGenerator, Protocol

logger = logging.getLogger(__name__)


class Status(Enum):
    PENDING = "pending"
    ACTIVE = "active"
    SUSPENDED = "suspended"
    DELETED = "deleted"


class Repository(Protocol):
    async def find(self, id: int) -> dict[str, Any] | None: ...
    async def save(self, entity: dict[str, Any]) -> None: ...
    async def delete(self, id: int) -> bool: ...


@dataclass(frozen=True, slots=True)
class UserDTO:
    id: int
    name: str
    email: str
    status: Status = Status.ACTIVE
    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))

    @property
    def is_active(self) -> bool:
        return self.status == Status.ACTIVE

    def to_dict(self) -> dict[str, Any]:
        return {
            "id": self.id,
            "name": self.name,
            "email": self.email,
            "status": self.status.value,
            "created_at": self.created_at.isoformat(),
        }


class UserService:
    def __init__(self, repository: Repository, cache_ttl: int = 3600) -> None:
        self._repository = repository
        self._cache: dict[int, UserDTO] = {}
        self._cache_ttl = cache_ttl
        self._lock = asyncio.Lock()

    async def get_user(self, user_id: int) -> UserDTO | None:
        if user_id in self._cache:
            return self._cache[user_id]

        async with self._lock:
            data = await self._repository.find(user_id)
            if data is None:
                logger.warning("User %d not found", user_id)
                return None

            user = UserDTO(
                id=data["id"],
                name=data["name"],
                email=data["email"],
                status=Status(data.get("status", "active")),
            )
            self._cache[user_id] = user
            return user

    async def get_active_users(self) -> AsyncGenerator[UserDTO, None]:
        for user_id, user in self._cache.items():
            if user.is_active:
                yield user

    async def create_user(self, name: str, email: str) -> UserDTO:
        user_data = {
            "name": name,
            "email": email,
            "status": Status.ACTIVE.value,
            "created_at": datetime.now(timezone.utc).isoformat(),
        }

        await self._repository.save(user_data)
        user = UserDTO(id=user_data.get("id", 0), name=name, email=email)
        self._cache[user.id] = user

        logger.info("Created user: %s <%s>", name, email)
        return user

    async def deactivate_user(self, user_id: int) -> bool:
        user = await self.get_user(user_id)
        if user is None or not user.is_active:
            return False

        try:
            await self._repository.save(
                {**user.to_dict(), "status": Status.SUSPENDED.value}
            )
            self._cache.pop(user_id, None)
            logger.info("Deactivated user %d", user_id)
            return True
        except Exception as exc:
            logger.error("Failed to deactivate user %d: %s", user_id, exc)
            raise
