"""Async Meemo Python client library for interacting with the Meemo External API.

This library provides an async interface to interact with the Meemo External API,
supporting OAuth2 Client Credentials authentication and meeting data access.

Example:
    >>> async_client = AsyncMeemoClient(
    ...     client_id="your-client-id",
    ...     client_secret="your-client-secret",
    ...     base_url="https://your-meemo-instance.com",
    ... )
    >>> # List meetings
    >>> meetings = await async_client.meetings.list_meetings()
    >>> print(meetings.count)
    >>>
    >>> # Get meeting details
    >>> detail = await async_client.meetings.get_meeting(123)
    >>> print(detail.title)

Authors:
    GDP Labs

References:
    https://gdplabs.gitbook.io/meemo/resources/external-api-documentation
"""

import logging
from datetime import datetime, timedelta

import httpx

from meemo._base import _BaseClient
from meemo.async_meetings import AsyncMeetings
from meemo.async_usage import AsyncUsage
from meemo.async_webhooks import AsyncWebhooks
from meemo.models import TokenResponse

logger = logging.getLogger(__name__)


class AsyncMeemoClient(_BaseClient):
    """Async Meemo External API Client.

    Uses OAuth2 Client Credentials flow for authentication. Tokens are
    automatically obtained and refreshed when they expire.

    Attributes:
        meetings (AsyncMeetings): AsyncMeetings instance for meeting data operations.
        webhooks (AsyncWebhooks): AsyncWebhooks instance for webhook management.
        usage (AsyncUsage): AsyncUsage instance for usage data operations.
    """

    def __init__(
        self,
        client_id: str | None = None,
        client_secret: str | None = None,
        base_url: str | None = None,
        timeout: float = 60.0,
        default_headers: dict[str, str] | None = None,
    ):
        """Initialize AsyncMeemoClient."""
        super().__init__(client_id, client_secret, base_url, timeout, default_headers)
        self.meetings = AsyncMeetings(self)
        self.webhooks = AsyncWebhooks(self)
        self.usage = AsyncUsage(self)

    async def get_access_token(self) -> str:
        """Get a valid access token, refreshing if necessary.

        Returns a cached token if it is still valid, or requests a new one
        from the OAuth2 token endpoint.

        Returns:
            str: A valid access token.

        Raises:
            httpx.HTTPStatusError: If the token request fails.
        """
        if (
            self._access_token
            and self._token_expires_at
            and self._token_expires_at > datetime.now()
        ):
            return self._access_token

        logger.debug("Requesting new access token from %s", self.base_url)

        url = f"{self.base_url}/api/auth/token/"
        timeout = httpx.Timeout(self.timeout)

        async with httpx.AsyncClient(timeout=timeout) as http_client:
            response = await http_client.post(
                url,
                data={
                    "grant_type": "client_credentials",
                    "client_id": self.client_id,
                    "client_secret": self.client_secret,
                },
                headers={"Content-Type": "application/x-www-form-urlencoded"},
            )
            response.raise_for_status()
            token_data = TokenResponse(**response.json())

        self._access_token = token_data.access_token
        # Refresh 60 seconds before actual expiry to avoid edge cases
        self._token_expires_at = datetime.now() + timedelta(seconds=token_data.expires_in - 60)

        logger.debug("Access token obtained, expires in %d seconds", token_data.expires_in)
        return self._access_token

    async def revoke_token(self) -> None:
        """Revoke the current access token.

        If no token is currently held, this method does nothing.

        Raises:
            httpx.HTTPStatusError: If the revoke request fails.
        """
        if not self._access_token:
            logger.debug("No token to revoke")
            return

        logger.debug("Revoking access token")

        url = f"{self.base_url}/api/auth/revoke-token/"
        timeout = httpx.Timeout(self.timeout)

        async with httpx.AsyncClient(timeout=timeout) as http_client:
            response = await http_client.post(
                url,
                data={
                    "client_id": self.client_id,
                    "client_secret": self.client_secret,
                    "token": self._access_token,
                },
                headers={"Content-Type": "application/x-www-form-urlencoded"},
            )
            response.raise_for_status()

        self._access_token = None
        self._token_expires_at = None
        logger.debug("Access token revoked successfully")

    async def _prepare_headers(self, extra_headers: dict[str, str] | None = None) -> dict[str, str]:
        """Prepare headers for API requests, including Bearer token.

        Args:
            extra_headers: Additional headers to merge with defaults.

        Returns:
            Dictionary of request headers.
        """
        headers = self.default_headers.copy()
        token = await self.get_access_token()
        headers["Authorization"] = f"Bearer {token}"
        if extra_headers:
            headers.update(extra_headers)
        return headers

    async def _make_request(
        self,
        method: str,
        url: str,
        headers: dict[str, str],
        json_data: dict | None = None,
        params: dict | None = None,
    ) -> dict | list:
        """Make an async HTTP request to the API.

        Args:
            method: HTTP method (GET, POST, DELETE, PUT).
            url: Request URL.
            headers: Request headers.
            json_data: Optional JSON body.
            params: Optional query parameters.

        Returns:
            Response JSON as dict or list.

        Raises:
            httpx.HTTPStatusError: If the request fails.
            TypeError: If response body is not dict or list.
        """
        timeout = httpx.Timeout(self.timeout)
        logger.debug("Request: %s %s", method, url)
        if json_data:
            logger.debug("Body: %s", json_data)
        async with httpx.AsyncClient(timeout=timeout) as http_client:
            response = await http_client.request(
                method=method,
                url=url,
                headers=headers,
                json=json_data,
                params=params,
            )
            response.raise_for_status()
            if response.status_code == 204 or not response.content:
                return {}
            try:
                data = response.json()
            except Exception:
                return {}
            if not isinstance(data, (dict, list)):
                raise TypeError(f"Unexpected response type: {type(data)}")
            return data
