"""سرویس کمکی برای resolve کردن کاربر Core از روی CurrentUser (JWT).

توکن ممکن است از دو منبع باشد:
    ۱. JWT محلی OTP (HS256) — sub برابر با users.id است.
    ۲. JWT مرکزی Keycloak (RS256) — sub برابر با users.keycloak_sub است.

اگر کاربر SSO هنوز به رکورد Core لینک نشده باشد، با موبایل/نام‌کاربری
(JIT) لینک یا ساخته می‌شود — نه اینکه پیام «تأیید موبایل» بدهد وقتی موبایل
قبلاً تأیید شده است.
"""
from __future__ import annotations

from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.core.config import settings
from app.models.enums import UserRole, UserStatus
from app.models.user import User
from app.repositories.user import UserRepository
from app.utils.phone import normalize_mobile
from shared.exceptions import ForbiddenError
from shared.security import CurrentUser


class UserService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.repo = UserRepository(session)

    async def resolve_current(self, current_user: CurrentUser) -> User:
        """کاربر Core متناظر با هویت جاری را پیدا می‌کند (با JIT link در صورت نیاز)."""
        candidate_id: UUID | None = None
        try:
            candidate_id = UUID(str(current_user.user_id))
        except (ValueError, TypeError, AttributeError):
            candidate_id = None

        if candidate_id is not None:
            user = await self.repo.get(candidate_id)
            if user is not None:
                return user

        sub = str(current_user.user_id)
        user = await self.repo.get_by_keycloak_sub(sub)
        if user is not None:
            return user

        # JIT: لینک/ساخت از روی موبایل یا username (معمولاً همان موبایل)
        linked = await self._jit_link_or_create(current_user)
        if linked is not None:
            return linked

        raise ForbiddenError(
            "پروفایل کاربری هسته یافت نشد. ابتدا شماره موبایل خود را تأیید کنید.",
            error_code="core_profile_not_linked",
        )

    async def _jit_link_or_create(self, current_user: CurrentUser) -> User | None:
        mobile = self._extract_mobile(current_user)
        if not mobile:
            return None

        sub = str(current_user.user_id)
        existing = await self.repo.get_by_mobile(mobile)
        if existing is not None:
            if existing.keycloak_sub and existing.keycloak_sub != sub:
                raise ForbiddenError(
                    "این شماره موبایل به حساب دیگری متصل است.",
                    error_code="mobile_linked_elsewhere",
                )
            existing.keycloak_sub = sub
            existing.mobile_verified = True
            if current_user.email and not existing.email:
                existing.email = current_user.email
            await self.session.commit()
            await self.session.refresh(existing)
            return existing

        role = (
            UserRole.PLATFORM_ADMIN
            if mobile in settings.platform_admin_mobile_set
            else UserRole.USER
        )
        user = User(
            mobile=mobile,
            role=role,
            status=UserStatus.ACTIVE,
            mobile_verified=True,
            keycloak_sub=sub,
            email=current_user.email,
        )
        await self.repo.add(user)
        await self.session.commit()
        await self.session.refresh(user)
        return user

    @staticmethod
    def _extract_mobile(current_user: CurrentUser) -> str | None:
        candidates: list[str] = []
        if current_user.username:
            candidates.append(current_user.username)
        if current_user.email and "@" in current_user.email:
            local = current_user.email.split("@", 1)[0]
            # ایمیل مصنوعی ثبت‌نام موبایل: 9155105404@mobile.torbatyar.local
            if local.isdigit() and len(local) >= 10:
                candidates.append("0" + local if not local.startswith("0") else local)
            else:
                candidates.append(local)

        for raw in candidates:
            try:
                return normalize_mobile(raw)
            except ValueError:
                continue
        return None
