"""Webhook handling for the GL Speech Python client.

This module provides the Webhooks class for handling webhook operations
with the Prosa Speech API, including endpoint management, event handling,
and delivery management.

Authors:
    GDP Labs

References:
    https://docs2.prosa.ai/speech/webhook/rest/api/
"""

import logging
from typing import Any
from urllib.parse import urljoin

import httpx

from gl_speech_sdk.models import (
    DeliveryTicket,
    WebhookDelivery,
    WebhookEndpoint,
    WebhookEndpointCreate,
    WebhookEndpointListing,
    WebhookEndpointUpdate,
    WebhookEvent,
    WebhookEventListing,
    WebhookRotation,
    WebhookRotationPeriod,
)

logger = logging.getLogger(__name__)


class Webhooks:
    """Handles Webhook API operations for the Prosa Speech API."""

    def __init__(self, client):
        """Initialize Webhooks API.

        Args:
            client: SpeechClient instance
        """
        self._client = client

    def _prepare_headers(
        self, extra_headers: dict[str, str] | None = None
    ) -> dict[str, str]:
        """Prepare headers for the API request.

        Args:
            extra_headers (dict[str, str] | None): Additional headers to merge with default headers

        Returns:
            dict[str, str]: Dictionary containing the request headers
        """
        headers = self._client.default_headers.copy()

        if self._client.api_key:
            headers["x-api-key"] = self._client.api_key

        if extra_headers:
            headers.update(extra_headers)

        return headers

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

        Args:
            method (str): HTTP method (GET, POST, DELETE, PUT)
            url (str): Request URL
            headers (dict[str, str]): Request headers
            json_data (dict[str, Any] | None): JSON body data
            params (dict[str, Any] | None): Query parameters

        Returns:
            dict[str, Any] | list[dict[str, Any]] | int: Response JSON data

        Raises:
            httpx.HTTPStatusError: If the request fails
        """
        timeout = httpx.Timeout(self._client.timeout)

        logger.debug("Request: %s %s", method, url)
        logger.debug("Headers: %s", headers)
        if json_data:
            logger.debug("Body: %s", json_data)

        with httpx.Client(timeout=timeout) as client:
            response = 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, int)):
                raise TypeError(f"Unexpected response type: {type(data)}")
            return data

    # =========================================================================
    # Endpoint Management
    # =========================================================================

    def list_endpoints(
        self,
        extra_headers: dict[str, str] | None = None,
    ) -> list[WebhookEndpointListing]:
        """List all webhook endpoints.

        Args:
            extra_headers (dict[str, str] | None): Additional headers

        Returns:
            list[WebhookEndpointListing]: List of webhook endpoints

        Raises:
            httpx.HTTPStatusError: If the API request fails
        """
        logger.debug("Listing webhook endpoints")

        url = urljoin(self._client.base_url, "webhooks/endpoints")
        headers = self._prepare_headers(extra_headers)

        response_data = self._make_request("GET", url, headers)
        if not isinstance(response_data, list):
            return []
        return [WebhookEndpointListing(**item) for item in response_data]

    def create_endpoint(
        self,
        url: str,
        event_filters: list[str] | None = None,
        ssl_verification: bool | None = True,
        secret_key: str | None = None,
        extra_headers: dict[str, str] | None = None,
    ) -> WebhookEndpoint:
        """Create a new webhook endpoint.

        Args:
            url (str): Callback URL for webhook events
            event_filters (list[str] | None): Event types to filter. Empty = all events.
            ssl_verification (bool | None): Verify SSL certificate. Default: True.
            secret_key (str | None): Secret key for signing. Empty = auto-generated.
            extra_headers (dict[str, str] | None): Additional headers

        Returns:
            WebhookEndpoint: Created webhook endpoint with secrets

        Raises:
            ValueError: If url is empty
            httpx.HTTPStatusError: If the API request fails
        """
        if not url:
            raise ValueError("url cannot be empty")

        logger.debug("Creating webhook endpoint: %s", url)

        endpoint_url = urljoin(self._client.base_url, "webhooks/endpoints")
        headers = self._prepare_headers(extra_headers)

        request = WebhookEndpointCreate(
            url=url,
            event_filters=event_filters,
            ssl_verification=ssl_verification,
            secret_key=secret_key,
        )
        json_data = request.model_dump(exclude_none=True)

        response_data = self._make_request("POST", endpoint_url, headers, json_data)
        if not isinstance(response_data, dict):
            raise TypeError("Expected dict response from API")
        return WebhookEndpoint(**response_data)

    def get_endpoint(
        self,
        endpoint_id: str,
        extra_headers: dict[str, str] | None = None,
    ) -> WebhookEndpoint:
        """Get details of a specific webhook endpoint.

        Args:
            endpoint_id (str): Unique identifier of the endpoint
            extra_headers (dict[str, str] | None): Additional headers

        Returns:
            WebhookEndpoint: Webhook endpoint details with secrets

        Raises:
            ValueError: If endpoint_id is empty
            httpx.HTTPStatusError: If the API request fails
        """
        if not endpoint_id:
            raise ValueError("endpoint_id cannot be empty")

        logger.debug("Getting webhook endpoint: %s", endpoint_id)

        url = urljoin(self._client.base_url, f"webhooks/endpoints/{endpoint_id}")
        headers = self._prepare_headers(extra_headers)

        response_data = self._make_request("GET", url, headers)
        if not isinstance(response_data, dict):
            raise TypeError("Expected dict response from API")
        return WebhookEndpoint(**response_data)

    def update_endpoint(
        self,
        endpoint_id: str,
        url: str | None = None,
        event_filters: list[str] | None = None,
        ssl_verification: bool | None = True,
        extra_headers: dict[str, str] | None = None,
    ) -> WebhookEndpoint:
        """Update an existing webhook endpoint.

        Args:
            endpoint_id (str): Unique identifier of the endpoint
            url (str | None): Callback URL for webhook events
            event_filters (list[str] | None): Event types to filter. Empty = all events.
            ssl_verification (bool | None): Verify SSL certificate. Default: True.
            extra_headers (dict[str, str] | None): Additional headers

        Returns:
            WebhookEndpoint: Updated webhook endpoint

        Raises:
            ValueError: If endpoint_id is empty
            httpx.HTTPStatusError: If the API request fails
        """
        if not endpoint_id:
            raise ValueError("endpoint_id cannot be empty")

        logger.debug("Updating webhook endpoint: %s", endpoint_id)

        endpoint_url = urljoin(self._client.base_url, f"webhooks/endpoints/{endpoint_id}")
        headers = self._prepare_headers(extra_headers)

        request = WebhookEndpointUpdate(
            url=url,
            event_filters=event_filters,
            ssl_verification=ssl_verification,
        )
        json_data = request.model_dump(exclude_none=True)

        response_data = self._make_request("PUT", endpoint_url, headers, json_data)
        if not isinstance(response_data, dict):
            raise TypeError("Expected dict response from API")
        return WebhookEndpoint(**response_data)

    def delete_endpoint(
        self,
        endpoint_id: str,
        extra_headers: dict[str, str] | None = None,
    ) -> WebhookEndpoint:
        """Delete a webhook endpoint.

        Args:
            endpoint_id (str): Unique identifier of the endpoint
            extra_headers (dict[str, str] | None): Additional headers

        Returns:
            WebhookEndpoint: Deleted webhook endpoint details

        Raises:
            ValueError: If endpoint_id is empty
            httpx.HTTPStatusError: If the API request fails
        """
        if not endpoint_id:
            raise ValueError("endpoint_id cannot be empty")

        logger.debug("Deleting webhook endpoint: %s", endpoint_id)

        url = urljoin(self._client.base_url, f"webhooks/endpoints/{endpoint_id}")
        headers = self._prepare_headers(extra_headers)

        response_data = self._make_request("DELETE", url, headers)
        if not response_data:
            # Return dummy but required fields if response is empty
            return WebhookEndpoint(id=endpoint_id, url="", ssl_verification=True)
        if not isinstance(response_data, dict):
            raise TypeError("Expected dict response from API")
        return WebhookEndpoint(**response_data)

    def rotate_secret(
        self,
        endpoint_id: str,
        days: int | None = 3,
        hours: int | None = 0,
        extra_headers: dict[str, str] | None = None,
    ) -> WebhookEndpoint:
        """Rotate the secret key for a webhook endpoint.

        Args:
            endpoint_id (str): Unique identifier of the endpoint
            days (int | None): Days old secret remains valid. Default: 3.
            hours (int | None): Hours old secret remains valid. Default: 0.
            extra_headers (dict[str, str] | None): Additional headers

        Returns:
            WebhookEndpoint: Webhook endpoint with new and expiring secrets

        Raises:
            ValueError: If endpoint_id is empty
            httpx.HTTPStatusError: If the API request fails
        """
        if not endpoint_id:
            raise ValueError("endpoint_id cannot be empty")

        logger.debug("Rotating secret for webhook endpoint: %s", endpoint_id)

        url = urljoin(self._client.base_url, f"webhooks/endpoints/{endpoint_id}/secret")
        headers = self._prepare_headers(extra_headers)

        period = WebhookRotationPeriod(days=days, hours=hours)
        request = WebhookRotation(rotation_period=period)
        json_data = request.model_dump(exclude_none=True)

        response_data = self._make_request("POST", url, headers, json_data)
        if not isinstance(response_data, dict):
            raise TypeError("Expected dict response from API")
        return WebhookEndpoint(**response_data)

    # =========================================================================
    # Event Handling
    # =========================================================================

    def list_events(
        self,
        from_date: str | None = None,
        until_date: str | None = None,
        extra_headers: dict[str, str] | None = None,
    ) -> list[WebhookEventListing]:
        """List webhook events.

        Args:
            from_date (str | None): Filter events from this date (YYYY-MM-DD)
            until_date (str | None): Filter events until this date (YYYY-MM-DD)
            extra_headers (dict[str, str] | None): Additional headers

        Returns:
            list[WebhookEventListing]: List of webhook events

        Raises:
            httpx.HTTPStatusError: If the API request fails
        """
        logger.debug("Listing webhook events")

        url = urljoin(self._client.base_url, "webhooks/events")
        headers = self._prepare_headers(extra_headers)

        params: dict[str, Any] = {}
        if from_date is not None:
            params["from_date"] = from_date
        if until_date is not None:
            params["until_date"] = until_date

        response_data = self._make_request("GET", url, headers, params=params)
        if not isinstance(response_data, list):
            return []
        return [WebhookEventListing(**item) for item in response_data]

    def get_event(
        self,
        event_id: str,
        extra_headers: dict[str, str] | None = None,
    ) -> WebhookEvent:
        """Get details of a specific webhook event.

        Args:
            event_id (str): Unique identifier of the event
            extra_headers (dict[str, str] | None): Additional headers

        Returns:
            WebhookEvent: Webhook event details with data payload

        Raises:
            ValueError: If event_id is empty
            httpx.HTTPStatusError: If the API request fails
        """
        if not event_id:
            raise ValueError("event_id cannot be empty")

        logger.debug("Getting webhook event: %s", event_id)

        url = urljoin(self._client.base_url, f"webhooks/events/{event_id}")
        headers = self._prepare_headers(extra_headers)

        response_data = self._make_request("GET", url, headers)
        if not isinstance(response_data, dict):
            raise TypeError("Expected dict response from API")
        return WebhookEvent(**response_data)

    def test_endpoint(
        self,
        endpoint_id: str,
        extra_headers: dict[str, str] | None = None,
    ) -> DeliveryTicket:
        """Trigger a test event for a webhook endpoint.

        Args:
            endpoint_id (str): Unique identifier of the endpoint
            extra_headers (dict[str, str] | None): Additional headers

        Returns:
            DeliveryTicket: Test event delivery ticket

        Raises:
            ValueError: If endpoint_id is empty
            httpx.HTTPStatusError: If the API request fails
        """
        if not endpoint_id:
            raise ValueError("endpoint_id cannot be empty")

        logger.debug("Testing webhook endpoint: %s", endpoint_id)

        url = urljoin(self._client.base_url, f"webhooks/endpoints/{endpoint_id}/test")
        headers = self._prepare_headers(extra_headers)

        response_data = self._make_request("POST", url, headers)
        if not isinstance(response_data, dict):
            raise TypeError("Expected dict response from API")
        return DeliveryTicket(**response_data)

    # =========================================================================
    # Delivery Management
    # =========================================================================

    def list_deliveries(
        self,
        endpoint_id: str,
        from_date: str | None = None,
        until_date: str | None = None,
        extra_headers: dict[str, str] | None = None,
    ) -> list[WebhookDelivery]:
        """List webhook deliveries for an endpoint.

        Args:
            endpoint_id (str): Unique identifier of the endpoint
            from_date (str | None): Filter deliveries from this date (YYYY-MM-DD)
            until_date (str | None): Filter deliveries until this date (YYYY-MM-DD)
            extra_headers (dict[str, str] | None): Additional headers

        Returns:
            list[WebhookDelivery]: List of webhook deliveries

        Raises:
            ValueError: If endpoint_id is empty
            httpx.HTTPStatusError: If the API request fails
        """
        if not endpoint_id:
            raise ValueError("endpoint_id cannot be empty")

        logger.debug("Listing webhook deliveries for endpoint: %s", endpoint_id)

        url = urljoin(self._client.base_url, f"webhooks/endpoints/{endpoint_id}/deliveries")
        headers = self._prepare_headers(extra_headers)

        params: dict[str, Any] = {}
        if from_date is not None:
            params["from_date"] = from_date
        if until_date is not None:
            params["until_date"] = until_date

        response_data = self._make_request("GET", url, headers, params=params)
        if not isinstance(response_data, list):
            return []
        return [WebhookDelivery(**item) for item in response_data]

    def replay_delivery(
        self,
        delivery_id: str,
        extra_headers: dict[str, str] | None = None,
    ) -> DeliveryTicket:
        """Replay a specific webhook delivery.

        Args:
            delivery_id (str): Unique identifier of the delivery
            extra_headers (dict[str, str] | None): Additional headers

        Returns:
            DeliveryTicket: Delivery ticket for the replayed event

        Raises:
            ValueError: If delivery_id is empty
            httpx.HTTPStatusError: If the API request fails
        """
        if not delivery_id:
            raise ValueError("delivery_id cannot be empty")

        logger.debug("Replaying webhook delivery: %s", delivery_id)

        url = urljoin(self._client.base_url, f"webhooks/deliveries/{delivery_id}/replay")
        headers = self._prepare_headers(extra_headers)

        response_data = self._make_request("POST", url, headers)
        if not isinstance(response_data, dict):
            raise TypeError("Expected dict response from API")
        return DeliveryTicket(**response_data)

    def replay_failed_deliveries(
        self,
        endpoint_id: str,
        extra_headers: dict[str, str] | None = None,
    ) -> list[DeliveryTicket]:
        """Replay all failed deliveries for an endpoint.

        Args:
            endpoint_id (str): Unique identifier of the endpoint
            extra_headers (dict[str, str] | None): Additional headers

        Returns:
            list[DeliveryTicket]: List of new delivery tickets

        Raises:
            ValueError: If endpoint_id is empty
            httpx.HTTPStatusError: If the API request fails
        """
        if not endpoint_id:
            raise ValueError("endpoint_id cannot be empty")

        logger.debug("Replaying failed deliveries for endpoint: %s", endpoint_id)

        url = urljoin(
            self._client.base_url,
            f"webhooks/endpoints/{endpoint_id}/replay-failed-deliveries"
        )
        headers = self._prepare_headers(extra_headers)

        response_data = self._make_request("POST", url, headers)
        if not isinstance(response_data, list):
            return []
        return [DeliveryTicket(**item) for item in response_data]
