"""Detection of consent / service-principal authentication errors.

When the CLI targets the direct WorkIQ A2A endpoint but the tenant has not been
provisioned for that service (the WorkIQ service principal is missing or admin
consent has not been granted), token acquisition fails with a recognizable set
of Entra ID (AADSTS) error codes — or, when a platform broker (WAM) is in use,
with a broker-specific indicator. This module recognizes those errors so the
caller can fall back to the Microsoft Graph gateway, mirroring the WorkIQ CLI's
``ConsentErrorDetector``.

The detector is intentionally pure (no MSAL import) so it can be unit-tested in
isolation and applied to either an MSAL result dict or a raised exception.
"""

from typing import Any, Optional

# AADSTS codes indicating the tenant is not provisioned / consent missing for
# the requested resource (rather than a genuine, non-recoverable auth failure).
#   AADSTS650052 - app needs access to a service the org has not subscribed to
#                  (WorkIQ service principal not provisioned).
#   AADSTS65001  - user or administrator has not consented to the application.
#   AADSTS70011  - requested scope is not recognized for the resource.
CONSENT_AADSTS_CODES = ("AADSTS650052", "AADSTS65001", "AADSTS70011")

# The same codes also appear bare (no "AADSTS" prefix) in MSAL's numeric
# ``error_codes`` list, so match those too.
_NUMERIC_CONSENT_CODES = tuple(
    code.removeprefix("AADSTS") for code in CONSENT_AADSTS_CODES
)

# Broker (WAM) surfaces the same conditions without an AADSTS string.
WAM_CONSENT_INDICATORS = ("IncorrectConfiguration",)
WAM_INTERNAL_SUBCODE = "3399614468"

_ALL_INDICATORS = (
    CONSENT_AADSTS_CODES
    + _NUMERIC_CONSENT_CODES
    + WAM_CONSENT_INDICATORS
    + (WAM_INTERNAL_SUBCODE,)
)


def extract_error_text(result_or_exc: Any) -> Optional[str]:
    """Flatten an MSAL failure into a single searchable string.

    Accepts either the MSAL result dict (which on failure carries ``error``,
    ``error_description``, ``error_codes`` and/or ``suberror``) or a raised
    exception. Returns ``None`` when there is nothing to inspect.
    """
    if result_or_exc is None:
        return None

    if isinstance(result_or_exc, dict):
        parts = []
        for key in ("error", "error_description", "suberror"):
            value = result_or_exc.get(key)
            if value:
                parts.append(str(value))
        # error_codes is a list of numeric AADSTS codes (without the prefix);
        # include them so WAM-style numeric subcodes are matchable too.
        error_codes = result_or_exc.get("error_codes")
        if isinstance(error_codes, (list, tuple)):
            parts.extend(str(code) for code in error_codes)
        elif error_codes:
            parts.append(str(error_codes))
        return " ".join(parts) if parts else None

    if isinstance(result_or_exc, BaseException):
        return str(result_or_exc)

    return str(result_or_exc)


def is_consent_or_sp_error(text: Optional[str]) -> bool:
    """Return True when *text* names a consent / service-principal error."""
    if not text:
        return False
    return any(indicator in text for indicator in _ALL_INDICATORS)
