from __future__ import annotations

import json
from typing import Optional

from ..config import XblAuthConfig
from ..msal import DeviceFlowClient
from ..storage import SecretStorage, KeyringSecretStorage
from ..xbox.user_auth import get_user_token
from ..xbox.xsts import get_xsts_token
from ..xbox.xbl3 import format_xbl3


KEYRING_SERVICE_NAME = "xbl3auth"


class Xbl3AuthService:
    """High-level service for securely obtaining XBL3.0 tokens."""

    def __init__(
        self,
        config: XblAuthConfig,
        account_id: str = "default",
        storage: Optional[SecretStorage] = None,
    ) -> None:
        """Initialize the Xbl3AuthService.

        Args:
            config: Authorization configuration for MSAL and Xbox Live.
            account_id: Logical account label used for keyring storage.
            storage: SecretStorage implementation, defaulting to KeyringSecretStorage.
        """
        self.config = config
        self.account_id = account_id
        self.storage = storage if storage is not None else KeyringSecretStorage()

        authority = f"{self.config.authority_url}/{self.config.tenant}"
        self._device_client = DeviceFlowClient(self.config.client_id, authority=authority)
        self._scopes = [self.config.scope]

    # ------------------------------------------------------------------
    # Refresh token helpers
    # ------------------------------------------------------------------

    def _store_refresh_token(self, refresh_token: str) -> None:
        self.storage.set_secret(KEYRING_SERVICE_NAME, self.account_id, refresh_token)

    def _load_refresh_token(self) -> Optional[str]:
        return self.storage.get_secret(KEYRING_SERVICE_NAME, self.account_id)

    def clear_refresh_token(self) -> None:
        """Remove any stored refresh token for this account."""
        self.storage.delete_secret(KEYRING_SERVICE_NAME, self.account_id)

    # ------------------------------------------------------------------
    # Access token handling
    # ------------------------------------------------------------------

    def get_access_token(self) -> str:
        """Obtain a valid Microsoft access token.

        Returns:
            Bearer access token for the configured scopes.

        Raises:
            RuntimeError: If a token cannot be obtained.
        """
        refresh_token = self._load_refresh_token()
        result = self._device_client.acquire_token(self._scopes, refresh_token=refresh_token)

        if not result:
            result = self._device_client.device_code_flow(self._scopes)

        access_token = result.get("access_token")
        if not access_token:
            raise RuntimeError("Unable to obtain access token from MSAL result.")

        new_refresh = result.get("refresh_token")
        if new_refresh:
            self._store_refresh_token(new_refresh)

        return access_token

    # ------------------------------------------------------------------
    # XBL3.0 token flow
    # ------------------------------------------------------------------

    def get_xbl3_token(self) -> str:
        """Obtain an XBL3.0 token string.

        Returns:
            XBL3.0 token formatted as 'XBL3.0 x=<uhs>;<token>'.

        Raises:
            RuntimeError: If access token retrieval fails or a downstream call errors.
        """
        access_token = self.get_access_token()
        _, user_token = get_user_token(access_token)
        user_hash, xsts_token = get_xsts_token(user_token)
        return format_xbl3(user_hash, xsts_token)

    # ------------------------------------------------------------------
    # Introspection helpers
    # ------------------------------------------------------------------

    def describe_state(self) -> str:
        """Return a minimal, non-secret description of the service state.

        Returns:
            JSON string describing account_id, tenant, and whether a refresh token is stored.
        """
        info = {
            "account_id": self.account_id,
            "tenant": self.config.tenant,
            "has_refresh_token": self._load_refresh_token() is not None,
        }
        return json.dumps(info)
