import os
from collections.abc import Sequence
from typing import Any

import msal

from home_agent.contracts.protocols import TokenStore


class MicrosoftAuthManager:
    def __init__(self, store: TokenStore, token_name: str = "microsoft") -> None:
        self.store: TokenStore = store
        self.token_name: str = token_name

    def init_device_code_auth(self, scopes: Sequence[str]) -> dict[str, Any]:
        app = self._build_app(cache=msal.SerializableTokenCache())
        flow = app.initiate_device_flow(scopes=list(scopes))
        message = flow.get("message")
        if isinstance(message, str):
            print(message)
        result = app.acquire_token_by_device_flow(flow)
        if "access_token" not in result:
            msg = f"microsoft auth failed: {result.get('error_description', result)}"
            raise RuntimeError(msg)
        self._save_cache(app)
        return result

    def get_access_token(self, scopes: Sequence[str]) -> str:
        cache = msal.SerializableTokenCache()
        cache_payload = self.store.load(self.token_name)
        if cache_payload is not None:
            serialized = cache_payload.get("cache")
            if isinstance(serialized, str):
                cache.deserialize(serialized)

        app = self._build_app(cache=cache)
        accounts = app.get_accounts()
        result: dict[str, Any] | None = None
        if accounts:
            result = app.acquire_token_silent(scopes=list(scopes), account=accounts[0])

        if result is None or "access_token" not in result:
            result = app.acquire_token_interactive(scopes=list(scopes))

        if "access_token" not in result:
            msg = f"microsoft token unavailable: {result.get('error_description', result)}"
            raise RuntimeError(msg)

        self._save_cache(app)
        token = result.get("access_token")
        if not isinstance(token, str):
            msg = "invalid microsoft access_token response"
            raise RuntimeError(msg)
        return token

    def _build_app(self, cache: msal.SerializableTokenCache) -> msal.PublicClientApplication:
        client_id = os.environ.get("MICROSOFT_CLIENT_ID")
        if not client_id:
            msg = "missing MICROSOFT_CLIENT_ID"
            raise RuntimeError(msg)
        tenant = os.environ.get("MICROSOFT_TENANT_ID", "consumers")
        authority = f"https://login.microsoftonline.com/{tenant}"
        return msal.PublicClientApplication(
            client_id=client_id,
            authority=authority,
            token_cache=cache,
        )

    def _save_cache(self, app: msal.PublicClientApplication) -> None:
        self.store.save(self.token_name, {"cache": app.token_cache.serialize()})
