import asyncio
import webbrowser

from supabase import AsyncClient
from supabase_auth.types import CodeExchangeParams  # noqa: F401 - used in cast

from harbor.auth.callback_server import wait_for_callback
from harbor.auth.client import create_authenticated_client
from harbor.auth.constants import CALLBACK_PORT
from harbor.auth.errors import AuthenticationError


class AuthHandler:
    """Manages Supabase authentication for the CLI (login, logout, status)."""

    def __init__(self) -> None:
        self._client: AsyncClient | None = None
        self._initialized = False

    async def initialize(self) -> None:
        """Create the Supabase async client."""
        self._client = await create_authenticated_client()
        self._initialized = True

    def _ensure_initialized(self) -> None:
        if not self._initialized:
            raise AuthenticationError(
                "AuthHandler not initialized. Call `await handler.initialize()` first."
            )

    async def login(self) -> str:
        """Run the OAuth login flow. Returns the GitHub username."""
        self._ensure_initialized()
        assert self._client is not None

        oauth_response = await self._client.auth.sign_in_with_oauth(
            {
                "provider": "github",
                "options": {
                    "redirect_to": f"http://localhost:{CALLBACK_PORT}/auth/callback",
                },
            }
        )

        webbrowser.open(oauth_response.url)

        auth_code = await wait_for_callback()

        # The SDK reads code_verifier from storage and redirect_to is optional
        # at runtime, but the TypedDict marks all keys as required.
        params = CodeExchangeParams(
            auth_code=auth_code, code_verifier="", redirect_to=""
        )
        session_response = await self._client.auth.exchange_code_for_session(params)

        session = session_response.session
        if session is None:
            raise AuthenticationError("Login succeeded but no session was returned.")

        user = session_response.user
        if user is None:
            return "unknown"
        return user.user_metadata.get("user_name") or user.email or user.id

    async def logout(self) -> None:
        """Sign out and clear stored credentials."""
        self._ensure_initialized()
        assert self._client is not None
        await self._client.auth.sign_out()

    async def is_authenticated(self) -> bool:
        """Check if a session exists on disk."""
        self._ensure_initialized()
        assert self._client is not None
        session = await self._client.auth.get_session()
        return session is not None

    async def get_github_username(self) -> str | None:
        """Return the current user's GitHub username, or None if not authenticated."""
        self._ensure_initialized()
        assert self._client is not None
        user = await self._client.auth.get_user()
        if user:
            return user.user.user_metadata.get("user_name") or user.user.email
        return None


_instance: AuthHandler | None = None
_init_lock: asyncio.Lock | None = None


def _get_init_lock() -> asyncio.Lock:
    global _init_lock
    if _init_lock is None:
        _init_lock = asyncio.Lock()
    return _init_lock


async def get_auth_handler() -> AuthHandler:
    """Return the shared AuthHandler singleton, initializing on first call."""
    global _instance
    if _instance is not None and _instance._initialized:
        return _instance
    async with _get_init_lock():
        if _instance is not None and _instance._initialized:
            return _instance
        handler = AuthHandler()
        await handler.initialize()
        _instance = handler
        return _instance
