from __future__ import annotations

from typing import Optional

import msal


class DeviceFlowClient:
    """Wrapper around MSAL's PublicClientApplication for device code flow and silent tokens."""

    def __init__(self, client_id: str, authority: str) -> None:
        """Initialize the device flow client.

        Args:
            client_id: Azure AD public client application ID.
            authority: Full authority URL including tenant (e.g. https://login.microsoftonline.com/consumers).
        """
        self._app = msal.PublicClientApplication(client_id, authority=authority)

    def acquire_token(self, scopes: list[str], refresh_token: Optional[str] = None) -> Optional[dict]:
        """Try to acquire an access token silently.

        This checks MSAL's cached accounts, then optionally uses a refresh token.

        Args:
            scopes: OAuth scopes to request.
            refresh_token: Optional refresh token to use if cache lookup fails.

        Returns:
            MSAL token result dictionary if successful, otherwise None.
        """
        accounts = self._app.get_accounts()
        if accounts:
            result = self._app.acquire_token_silent(scopes=scopes, account=accounts[0])
            if result and "access_token" in result:
                return result

        if refresh_token:
            result = self._app.acquire_token_by_refresh_token(refresh_token, scopes=scopes)
            if result and "access_token" in result:
                return result

        return None

    def device_code_flow(self, scopes: list[str]) -> dict:
        """Run the device code flow interactively.

        This function prints the verification URI and user code to stdout
        to guide the user through sign-in.

        Args:
            scopes: OAuth scopes to request.

        Returns:
            MSAL token result dictionary.

        Raises:
            RuntimeError: If the flow fails to start or no access token is returned.
        """
        flow = self._app.initiate_device_flow(scopes=scopes)
        if "user_code" not in flow:
            raise RuntimeError("Device code flow initiation failed.")

        print("\nTo sign in to your Microsoft account:")
        print(f"  1. Visit: {flow['verification_uri']}")
        print(f"  2. Enter code: {flow['user_code']}\n")

        result = self._app.acquire_token_by_device_flow(flow)
        if "access_token" not in result:
            error_desc = result.get("error_description", "Unknown error from MSAL.")
            raise RuntimeError(f"Failed to acquire token via device code flow: {error_desc}")

        return result
