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

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

Example:
    >>> client = MeemoClient(
    ...     client_id="your-client-id",
    ...     client_secret="your-client-secret",
    ...     base_url="https://your-meemo-instance.com",
    ... )
    >>> # List meetings
    >>> meetings = client.meetings.list_meetings()
    >>> print(meetings.count)
    >>>
    >>> # Get meeting details
    >>> detail = 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.meetings import Meetings
from meemo.models import TokenResponse
from meemo.usage import Usage
from meemo.webhooks import Webhooks

logger = logging.getLogger(__name__)


class MeemoClient(_BaseClient):
    """Meemo External API Client.

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

    Attributes:
        meetings (Meetings): Meetings instance for meeting data operations.
        webhooks (Webhooks): Webhooks instance for webhook management.
        usage (Usage): Usage 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 MeemoClient."""
        super().__init__(client_id, client_secret, base_url, timeout, default_headers)
        self.meetings = Meetings(self)
        self.webhooks = Webhooks(self)
        self.usage = Usage(self)

    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)

        with httpx.Client(timeout=timeout) as http_client:
            response = 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

    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)

        with httpx.Client(timeout=timeout) as http_client:
            response = 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")

    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 = self.get_access_token()
        headers["Authorization"] = f"Bearer {token}"
        if extra_headers:
            headers.update(extra_headers)
        return headers

    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 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)
        with httpx.Client(timeout=timeout) as http_client:
            response = 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
