"""Async HTTP client for the Graylog REST API."""

from __future__ import annotations

import base64
import re
from typing import Any

import httpx

from mcp_eregistrations_graylog.graylog_client.errors import (
    GraylogAuthError,
    GraylogClientError,
    GraylogConnectionError,
    GraylogNotFoundError,
    GraylogServerError,
    GraylogTimeoutError,
)

_DEFAULT_TIMEOUT = 30.0

# Relative time range units → seconds
_TIME_UNITS = {"s": 1, "m": 60, "h": 3600, "d": 86400, "w": 604800}


class GraylogClient:
    """Async HTTP client for Graylog REST API.

    Uses Basic Auth (not Keycloak). Credentials are either:
    - API token as username + literal "token" as password
    - Regular username + password

    Usage::

        async with GraylogClient(base_url="https://graylog.example.org",
                                  username="admin", password="admin") as c:
            info = await c.get("/api/system")
    """

    def __init__(
        self,
        base_url: str,
        username: str | None = None,
        password: str | None = None,
        timeout: float = _DEFAULT_TIMEOUT,
        verify_ssl: bool = True,
    ):
        self._base_url = base_url.rstrip("/")
        self._username = username
        self._password = password
        self._timeout = timeout
        self._verify_ssl = verify_ssl
        self._client: httpx.AsyncClient | None = None
        self._instance: str | None = None  # Set by for_instance()

    @staticmethod
    def parse_time_range(time_range: str | None) -> int:
        """Parse a relative time range string into seconds.

        Args:
            time_range: Relative time like '1h', '24h', '7d', '30m'.
                        None defaults to 3600 (1h).

        Returns:
            Time range in seconds.

        Raises:
            ValueError: If format is not recognized.
        """
        if time_range is None:
            return 3600  # default 1h

        match = re.fullmatch(r"(\d+)([smhdw])", time_range)
        if not match:
            raise ValueError(
                f"Invalid time range '{time_range}'. "
                f"Use format like '1h', '24h', '7d', '30m'."
            )
        value, unit = int(match.group(1)), match.group(2)
        return value * _TIME_UNITS[unit]

    async def __aenter__(self) -> GraylogClient:
        # Resolve credentials from auth_store if not provided
        if not self._username and self._instance:
            self._username, self._password = _resolve_credentials(self._instance)

        headers: dict[str, str] = {
            "Accept": "application/json",
        }
        if self._username and self._password:
            creds = f"{self._username}:{self._password}"
            encoded = base64.b64encode(creds.encode()).decode()
            headers["Authorization"] = f"Basic {encoded}"

        self._client = httpx.AsyncClient(
            base_url=self._base_url,
            headers=headers,
            timeout=self._timeout,
            verify=self._verify_ssl,
        )
        return self

    async def __aexit__(self, *args: Any) -> None:
        if self._client:
            await self._client.aclose()
            self._client = None

    def _raise_for_status(self, response: httpx.Response) -> None:
        """Translate HTTP status codes to Graylog errors."""
        if response.is_success:
            return
        status = response.status_code
        detail = ""
        try:
            body = response.json()
            detail = body.get("message", response.text)
        except Exception:
            detail = response.text

        if status == 401:
            raise GraylogAuthError(detail, status_code=status)
        if status == 403:
            raise GraylogAuthError(detail, status_code=status)
        if status == 404:
            raise GraylogNotFoundError(detail, status_code=status)
        if status >= 500:
            raise GraylogServerError(detail, status_code=status)
        raise GraylogClientError(f"HTTP {status}: {detail}", status_code=status)

    async def get(
        self,
        path: str,
        *,
        params: dict[str, Any] | None = None,
        timeout: float | None = None,
    ) -> Any:
        """GET request returning JSON response."""
        assert self._client is not None, (
            "GraylogClient must be used as async context manager"
        )
        kwargs: dict[str, Any] = {"params": params}
        if timeout is not None:
            kwargs["timeout"] = httpx.Timeout(timeout)
        try:
            response = await self._client.request("GET", path, **kwargs)
        except httpx.ConnectError as e:
            raise GraylogConnectionError(str(e)) from e
        except httpx.TimeoutException as e:
            raise GraylogTimeoutError(str(e)) from e
        self._raise_for_status(response)
        return response.json()

    @classmethod
    def for_instance(cls, instance: str | None = None) -> GraylogClient:
        """Create GraylogClient from instance profile config.

        Looks up graylog_url from common config. Credentials resolved
        lazily in __aenter__ from in-process store or auth_store.
        """
        from fastmcp.exceptions import ToolError

        from mcp_eregistrations_common.config import get_instance_config

        if instance is None:
            raise ToolError(
                "No instance specified. Pass instance='<name>'. "
                "Use instance_list to see available instances."
            )

        config = get_instance_config(instance)
        graylog_url = config.get("graylog_url")
        if not graylog_url:
            raise ToolError(
                f"No graylog_url configured for instance '{instance}'. "
                f"Use instance_add to register an instance with a graylog_url."
            )

        # Check in-process credential store
        username, password = None, None
        creds = _graylog_credentials.get(instance)
        if creds:
            username, password = creds

        client = cls(
            base_url=graylog_url,
            username=username,
            password=password,
            verify_ssl=config.get("verify_ssl", True),
        )
        client._instance = instance
        return client


# ---------------------------------------------------------------------------
# In-process credential store (populated by graylog_auth_login)
# ---------------------------------------------------------------------------

_graylog_credentials: dict[str, tuple[str, str]] = {}


def store_graylog_credentials(instance: str, username: str, password: str) -> None:
    """Store Graylog credentials in-process."""
    _graylog_credentials[instance] = (username, password)


def get_graylog_credentials(instance: str) -> tuple[str, str] | None:
    """Get stored Graylog credentials."""
    return _graylog_credentials.get(instance)


def _resolve_credentials(instance: str) -> tuple[str | None, str | None]:
    """Resolve Graylog credentials: in-process first, then auth_store."""
    # In-process
    creds = _graylog_credentials.get(instance)
    if creds:
        return creds

    # auth_store fallback
    try:
        from mcp_eregistrations_common.auth_store import _get_instance

        inst_data = _get_instance(instance)
        u = inst_data.get("graylog_username")
        p = inst_data.get("graylog_password")
        if u and p:
            # Cache for subsequent calls
            _graylog_credentials[instance] = (u, p)
            return u, p
    except Exception:
        pass

    return None, None
