from __future__ import annotations

import re
from typing import TYPE_CHECKING, Protocol
from uuid import UUID

from fastapi import HTTPException
from sqlalchemy.exc import SQLAlchemyError

from core.auth.models import CurrentUser
from core.db.base_service import BaseService
from core.logging import get_logger
from v1.users.repository import UserRepository
from v1.users.schemas import UserResponse, UserSearchRequest, UserUpdateRequest

if TYPE_CHECKING:
    from sqlalchemy.ext.asyncio import AsyncSession

    from v1.auth.schemas import UserByEmailResponse

logger = get_logger("v1.users.service")

_EMAIL_PATTERN = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")


class AuthLookupGateway(Protocol):
    async def get_user_id_by_email(self, email: str) -> str | None: ...


class AuthByEmailGateway(Protocol):
    async def get_user_by_email(self, email: str) -> "UserByEmailResponse": ...


class AuthLookupAdapter:
    def __init__(self, gateway: AuthByEmailGateway) -> None:
        self._gateway = gateway

    async def get_user_id_by_email(self, email: str) -> str | None:
        try:
            response = await self._gateway.get_user_by_email(email)
            return response.id
        except HTTPException:
            return None


class UserService(BaseService):
    """User service handling business logic and transactions.

    Responsibilities:
    - Authorization checks
    - Transaction boundary (commit/rollback)
    - Converting ORM models to response schemas
    """

    _repository: UserRepository
    _session: AsyncSession
    _auth_gateway: AuthLookupGateway | None

    def __init__(
        self,
        repository: UserRepository,
        session: AsyncSession,
        current_user: CurrentUser | None,
        auth_gateway: AuthLookupGateway | None = None,
    ) -> None:
        super().__init__(current_user=current_user)
        self._repository = repository
        self._session = session
        self._auth_gateway = auth_gateway

    async def get_me(self) -> UserResponse:
        user_id = self.require_user_id()
        try:
            user = await self._repository.get_by_user_id(user_id)
        except SQLAlchemyError:
            raise HTTPException(status_code=503, detail="User store unavailable")

        if user is None:
            raise HTTPException(status_code=404, detail="User not found")
        return UserResponse(
            id=str(user.id),
            username=user.username,
            avatar_url=user.avatar_url,
            bio=user.bio,
        )

    async def update_me(self, update: UserUpdateRequest) -> UserResponse:
        user_id = self.require_user_id()
        update_data: dict[str, str | None] = {
            key: value
            for key, value in {
                "username": update.username,
                "avatar_url": update.avatar_url,
                "bio": update.bio,
            }.items()
            if value is not None
        }

        if not update_data:
            raise HTTPException(status_code=400, detail="No fields to update")

        try:
            user = await self._repository.update_by_user_id(user_id, update_data)
            await self._session.commit()
        except SQLAlchemyError:
            await self._session.rollback()
            raise HTTPException(status_code=503, detail="User store unavailable")

        if user is None:
            raise HTTPException(status_code=404, detail="User not found")

        return UserResponse(
            id=str(user.id),
            username=user.username,
            avatar_url=user.avatar_url,
            bio=user.bio,
        )

    async def get_by_username(self, username: str) -> UserResponse:
        try:
            user = await self._repository.get_by_username(username)
        except SQLAlchemyError:
            raise HTTPException(status_code=503, detail="User store unavailable")

        if user is None:
            raise HTTPException(status_code=404, detail="User not found")
        return UserResponse(
            id=str(user.id),
            username=user.username,
            avatar_url=user.avatar_url,
            bio=user.bio,
        )

    async def search_users(self, request: UserSearchRequest) -> list[UserResponse]:
        query = request.query.strip()

        if _EMAIL_PATTERN.match(query):
            return await self._search_by_email(query)

        return await self._search_by_username(query)

    async def _search_by_email(self, email: str) -> list[UserResponse]:
        if self._auth_gateway is None:
            raise HTTPException(status_code=503, detail="Auth lookup unavailable")

        user_id_str = await self._auth_gateway.get_user_id_by_email(email)
        if user_id_str is None:
            return []

        try:
            user = await self._repository.get_by_user_id(UUID(user_id_str))
        except SQLAlchemyError:
            raise HTTPException(status_code=503, detail="User store unavailable")

        if user is None:
            return []

        return [
            UserResponse(
                id=str(user.id),
                username=user.username,
                avatar_url=user.avatar_url,
                bio=user.bio,
            )
        ]

    async def _search_by_username(self, query: str) -> list[UserResponse]:
        try:
            users = await self._repository.search_users(query, limit=20)
        except SQLAlchemyError:
            raise HTTPException(status_code=503, detail="User store unavailable")

        return [
            UserResponse(
                id=str(user.id),
                username=user.username,
                avatar_url=user.avatar_url,
                bio=user.bio,
            )
            for user in users
        ]
