"""Infomaniak Manager-API credentials + resolution.

The Manager API (hosting / domain / DNS / mail — **separate** from the OpenStack
Public Cloud that :mod:`mgf.cloud.config` resolves) authenticates with a
**Bearer token** minted in the Infomaniak Manager. This module resolves a
validated :class:`ManagerCredentials` from, in order: an explicit token, a token
file, then the ``$MGF_INFOMANIAK_TOKEN`` / ``$INFOMANIAK_API_TOKEN`` environment.

The token is held as a pydantic ``SecretStr`` so it never appears in logs,
reprs, or tracebacks (SC/EH). No HTTP client or SDK is imported here, so the
core stays light and this is unit-testable without a live account.
"""

from __future__ import annotations

import os
import re
from pathlib import Path
from typing import TYPE_CHECKING

from pydantic import BaseModel, ConfigDict, SecretStr

from mgf.cloud.exceptions import CredentialsError

if TYPE_CHECKING:
    from collections.abc import Mapping

__all__ = [
    "DEFAULT_BASE_URL",
    "ManagerCredentials",
    "from_env",
    "from_file",
    "from_token",
    "load_manager_credentials",
]

DEFAULT_BASE_URL = "https://api.infomaniak.com"
_ENV_VARS: tuple[str, ...] = ("MGF_INFOMANIAK_TOKEN", "INFOMANIAK_API_TOKEN")
# A token file may be bare, or the Manager's "token_name / token / scope" dump —
# pull the value after a leading ``token:`` (or ``token=``) label when present.
_LABELLED = re.compile(r"(?im)^\s*token\s*[:=]\s*(\S+)\s*$")


class ManagerCredentials(BaseModel):
    """A validated Infomaniak Manager Bearer token + the API base URL."""

    model_config = ConfigDict(frozen=True)

    token: SecretStr
    base_url: str = DEFAULT_BASE_URL

    def require_complete(self) -> ManagerCredentials:
        """Return self if usable; else raise :class:`CredentialsError`."""
        if not self.token.get_secret_value().strip():
            raise CredentialsError("empty Infomaniak Manager token", missing=("token",))
        if not self.base_url.startswith("https://"):
            raise CredentialsError(
                f"Manager base_url must be https, got {self.base_url!r}", missing=("base_url",)
            )
        return self


def _extract_token(text: str) -> str:
    """Return the bare token from ``text`` — a bare token, or the value after a
    leading ``token:`` label in the Manager's multi-line token dump."""
    match = _LABELLED.search(text)
    if match:
        return match.group(1)
    candidates = [line.strip() for line in text.splitlines() if line.strip()]
    return max(candidates, key=len) if candidates else ""


def from_token(token: str, *, base_url: str = DEFAULT_BASE_URL) -> ManagerCredentials:
    """Build credentials from an explicit token string."""
    return ManagerCredentials(token=SecretStr(token), base_url=base_url).require_complete()


def from_file(path: str | Path, *, base_url: str = DEFAULT_BASE_URL) -> ManagerCredentials:
    """Load a token from a file (bare, or a ``token:``-labelled Manager dump)."""
    file_path = Path(path)
    if not file_path.is_file():
        raise CredentialsError(
            f"Manager token file not found at {file_path}",
            source=str(file_path),
            missing=("token",),
        )
    token = _extract_token(file_path.read_text(encoding="utf-8"))
    if not token:
        raise CredentialsError(
            f"no token found in {file_path}", source=str(file_path), missing=("token",)
        )
    return ManagerCredentials(token=SecretStr(token), base_url=base_url)


def from_env(environ: Mapping[str, str] | None = None) -> ManagerCredentials:
    """Load a token from ``$MGF_INFOMANIAK_TOKEN`` / ``$INFOMANIAK_API_TOKEN``."""
    env = environ if environ is not None else os.environ
    for var in _ENV_VARS:
        value = env.get(var)
        if value:
            return ManagerCredentials(token=SecretStr(value)).require_complete()
    raise CredentialsError(
        f"no Infomaniak Manager token in the environment (set one of {', '.join(_ENV_VARS)})",
        source="env",
        missing=_ENV_VARS,
    )


def load_manager_credentials(
    token: str | None = None,
    token_file: str | Path | None = None,
    environ: Mapping[str, str] | None = None,
) -> ManagerCredentials:
    """Resolve credentials: explicit ``token`` → ``token_file`` → environment.

    Raises :class:`CredentialsError` naming what's missing if none resolve.
    """
    if token:
        return from_token(token)
    if token_file:
        return from_file(token_file)
    return from_env(environ)
