import asyncio
import webbrowser
from urllib.parse import parse_qs, urlparse

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,
        *,
        callback_url: str | None = None,
        open_browser: bool = True,
        allow_manual: bool = False,
    ) -> str:
        """Run the OAuth login flow. Returns the GitHub username."""
        return await self.login_with_options(
            callback_url=callback_url,
            open_browser=open_browser,
            allow_manual=allow_manual,
        )

    async def login_with_options(
        self,
        *,
        callback_url: str | None = None,
        open_browser: bool = True,
        allow_manual: bool = False,
    ) -> str:
        """Run the OAuth login flow with optional headless/manual fallback."""
        self._ensure_initialized()
        assert self._client is not None

        if callback_url is not None:
            auth_code = self._extract_auth_code(callback_url)
            return await self._exchange_auth_code(auth_code)

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

        if open_browser and self._open_in_browser(oauth_response.url):
            auth_code = await wait_for_callback()
            return await self._exchange_auth_code(auth_code)

        if not allow_manual:
            raise AuthenticationError(
                "Could not open a browser. Open this URL in another browser:\n"
                f"{oauth_response.url}\n"
                "Then rerun with --callback-url and paste the full redirected callback URL."
            )

        callback_input = self._prompt_for_callback_url(oauth_response.url)
        auth_code = self._extract_auth_code(callback_input)
        return await self._exchange_auth_code(auth_code)

    def _open_in_browser(self, url: str) -> bool:
        try:
            return bool(webbrowser.open(url))
        except Exception:
            return False

    def _prompt_for_callback_url(self, auth_url: str) -> str:
        print("Could not open a browser automatically.")
        print(f"Open this URL in a browser: {auth_url}")
        return input("Paste the full callback URL after signing in: ").strip()

    def _extract_auth_code(self, callback_input: str) -> str:
        callback_input = callback_input.strip()
        if not callback_input:
            raise AuthenticationError("No callback input was provided.")

        if callback_input.startswith("http://") or callback_input.startswith(
            "https://"
        ):
            parsed = urlparse(callback_input)
            query = parse_qs(parsed.query)
            callback_error = query.get("error")
            if callback_error:
                raise AuthenticationError(f"OAuth callback error: {callback_error[0]}")
            auth_codes = query.get("code")
            if not auth_codes:
                raise AuthenticationError(
                    "No authorization code found in callback URL. "
                    "Paste the full redirect URL shown after sign-in."
                )
            auth_code = auth_codes[0]
            if not auth_code:
                raise AuthenticationError(
                    "Authorization code in callback URL was empty."
                )
            return auth_code
        return callback_input

    async def _exchange_auth_code(self, auth_code: str) -> str:
        assert self._client is not None
        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
