import uuid
from typing import Any

from sqlalchemy.ext.asyncio import AsyncSession

from app.core.exceptions import ForbiddenError, NotFoundError
from app.models import User
from app.repositories import UserRepository, WalletRepository
from app.services.log_service import LogService, LogType
from app.utils.helpers import random_code


class UserService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.users = UserRepository(session)
        self.wallets = WalletRepository(session)
        self.logs = LogService(session)

    async def _unique_referral_code(self) -> str:
        for _ in range(10):
            code = random_code(8)
            if await self.users.get_by_referral_code(code) is None:
                return code
        return random_code(12)

    async def get_or_create(
        self,
        telegram_id: int,
        username: str | None = None,
        first_name: str | None = None,
        last_name: str | None = None,
        language: str = "fa",
        referral_code: str | None = None,
    ) -> tuple[User, bool]:
        """Register the Telegram user on first contact, then keep profile fresh."""
        user = await self.users.get_by_telegram_id(telegram_id)
        if user:
            changes: dict[str, Any] = {}
            if username != user.username:
                changes["username"] = username
            if first_name and first_name != user.first_name:
                changes["first_name"] = first_name
            if last_name != user.last_name:
                changes["last_name"] = last_name
            if changes:
                await self.users.update(user, **changes)
            return user, False

        referrer = None
        if referral_code:
            referrer = await self.users.get_by_referral_code(referral_code.strip())

        user = await self.users.create(
            telegram_id=telegram_id,
            username=username,
            first_name=first_name,
            last_name=last_name,
            language=language,
            referral_code=await self._unique_referral_code(),
            referred_by=referrer.id if referrer else None,
        )
        await self.wallets.create(user_id=user.id)
        await self.logs.write(
            LogType.SYSTEM,
            "user_registered",
            f"user {telegram_id} registered",
            user_id=user.id,
            data={"referred_by": str(referrer.id) if referrer else None},
        )
        return user, True

    async def get(self, user_id: uuid.UUID) -> User:
        user = await self.users.get(user_id)
        if not user:
            raise NotFoundError("user_not_found")
        return user

    async def get_by_telegram_id(self, telegram_id: int) -> User:
        user = await self.users.get_by_telegram_id(telegram_id)
        if not user:
            raise NotFoundError("user_not_found")
        if user.is_blocked:
            raise ForbiddenError("user_blocked")
        return user

    async def search(self, page: int, size: int, search: str | None = None) -> tuple[list[User], int]:
        return await self.users.search(page, size, search)

    async def update_profile(self, user: User, **values: Any) -> User:
        allowed = {"language", "note", "is_blocked", "is_admin"}
        payload = {key: value for key, value in values.items() if key in allowed and value is not None}
        return await self.users.update(user, **payload)
