"""Text-to-Speech handling for the GL Speech Python client.

This module provides the TextToSpeech class for handling TTS operations
with the Prosa Speech API, including speech synthesis, job management, and status tracking.

Authors:
    GDP Labs

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

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

import httpx

from gl_speech_sdk.models import (
    TTSConfig,
    TTSJobRequest,
    TTSJobsListResponse,
    TTSRequest,
    TTSResponse,
    TTSStatusResponse,
)

logger = logging.getLogger(__name__)


class TextToSpeech:
    """Handles Text-to-Speech API operations for the Prosa Speech API."""

    def __init__(self, client):
        """Initialize TextToSpeech 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 _prepare_request_data(
        self,
        model: str,
        text: str | None = None,
        ssml: str | None = None,
        wait: bool | None = None,
        pitch: float | None = None,
        tempo: float | None = None,
        audio_format: str | None = None,
        sample_rate: int | None = None,
        label: str | None = None,
    ) -> dict[str, Any]:
        """Prepare request data for the TTS API call.

        Args:
            model (str): TTS model name
            text (str | None): Text to synthesize
            pitch (float | None): Pitch offset
            tempo (float | None): Tempo adjustment
            audio_format (str | None): Output audio format
            sample_rate (int | None): Desired sample rate
            label (str | None): Job label

        Returns:
            dict[str, Any]: Dictionary containing the prepared request data
        """
        if not model:
            raise ValueError("model is required")

        config = TTSConfig(
            model=model,
            wait=wait,
            pitch=pitch,
            tempo=tempo,
            audio_format=audio_format,
            sample_rate=sample_rate,
        )

        request = TTSRequest(
            label=label,
            text=text,
            ssml=ssml,
        )

        job_request = TTSJobRequest(config=config, request=request)
        return job_request.model_dump(exclude_none=True)

    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 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

    def list_models(
        self,
        extra_headers: dict[str, str] | None = None,
    ) -> list[dict[str, Any]]:
        """List all available TTS models.

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

        Returns:
            list[dict[str, Any]]: List of available TTS models

        Raises:
            httpx.HTTPStatusError: If the API request fails
        """
        logger.debug("Listing available TTS models")

        url = urljoin(self._client.base_url, "tts/models")
        headers = self._prepare_headers(extra_headers)

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

    def synthesize(
        self,
        model: str,
        text: str | None = None,
        ssml: str | None = None,
        wait: bool | None = None,
        pitch: float | None = None,
        tempo: float | None = None,
        audio_format: str | None = None,
        sample_rate: int | None = None,
        label: str | None = None,
        as_signed_url: bool | None = None,
        extra_headers: dict[str, str] | None = None,
    ) -> TTSResponse:
        """Submit a text-to-speech synthesis request.

        Args:
            model (str): TTS model name (e.g., "tts-dimas-formal")
            text (str | None): Text to synthesize
            ssml (str | None): SSML to synthesize
            wait (bool | None): If True, blocks until execution finishes.
            pitch (float | None): Pitch offset of generated speech
            tempo (float | None): Tempo of generated speech
            audio_format (str | None): Output format: "opus", "mp3", or "wav"
            sample_rate (int | None): Desired sample rate
            label (str | None): Optional label for the job
            as_signed_url (bool | None): Return audio as signed URL instead of base64
            extra_headers (dict[str, str] | None): Additional headers

        Returns:
            TTSResponse: Synthesis response with job_id and possibly result

        Raises:
            ValueError: If neither text nor ssml is provided
            httpx.HTTPStatusError: If the API request fails
        """
        if not text and not ssml:
            raise ValueError("Either 'text' or 'ssml' must be provided")

        logger.debug("Submitting TTS synthesis request")

        url = urljoin(self._client.base_url, "tts")
        headers = self._prepare_headers(extra_headers)
        json_data = self._prepare_request_data(
            text=text,
            ssml=ssml,
            model=model,
            wait=wait,
            pitch=pitch,
            tempo=tempo,
            audio_format=audio_format,
            sample_rate=sample_rate,
            label=label,
        )

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

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

    def list_jobs(
        self,
        page: int | None = None,
        per_page: int | None = None,
        from_date: str | None = None,
        until_date: str | None = None,
        sort_by: str | None = None,
        sort_ascend: bool | None = None,
        query_text: str | None = None,
        extra_headers: dict[str, str] | None = None,
    ) -> TTSJobsListResponse:
        """List TTS jobs.

        Args:
            page (int | None): Page index for pagination
            per_page (int | None): Number of items per page
            from_date (str | None): Filter jobs from this date (YYYY-MM-DD)
            until_date (str | None): Filter jobs until this date (YYYY-MM-DD)
            sort_by (str | None): Field to sort by
            sort_ascend (bool | None): Sort in ascending order
            query_text (str | None): Search for jobs with matching request text
            extra_headers (dict[str, str] | None): Additional headers

        Returns:
            TTSJobsListResponse: List of TTS jobs with pagination

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

        url = urljoin(self._client.base_url, "tts")
        headers = self._prepare_headers(extra_headers)

        params: dict[str, Any] = {}
        if page is not None:
            params["page"] = page
        if per_page is not None:
            params["per_page"] = per_page
        if from_date is not None:
            params["from_date"] = from_date
        if until_date is not None:
            params["until_date"] = until_date
        if sort_by is not None:
            params["sort_by"] = sort_by
        if sort_ascend is not None:
            params["sort_ascend"] = sort_ascend
        if query_text is not None:
            params["query_text"] = query_text

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

    def get_job(
        self,
        job_id: str,
        as_signed_url: bool | None = None,
        extra_headers: dict[str, str] | None = None,
    ) -> TTSResponse:
        """Retrieve a specific TTS job by ID.

        Args:
            job_id (str): Unique identifier of the job
            as_signed_url (bool | None): Return audio as signed URL instead of base64
            extra_headers (dict[str, str] | None): Additional headers

        Returns:
            TTSResponse: Job details including result if complete

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

        logger.debug("Retrieving TTS job: %s", job_id)

        url = urljoin(self._client.base_url, f"tts/{job_id}")
        headers = self._prepare_headers(extra_headers)

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

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

    def get_status(
        self,
        job_id: str,
        extra_headers: dict[str, str] | None = None,
    ) -> TTSStatusResponse:
        """Retrieve the status of a specific TTS job.

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

        Returns:
            TTSStatusResponse: Job status information

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

        logger.debug("Retrieving TTS job status: %s", job_id)

        url = urljoin(self._client.base_url, f"tts/{job_id}/status")
        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 TTSStatusResponse(**response_data)

    def archive(
        self,
        job_id: str,
        extra_headers: dict[str, str] | None = None,
    ) -> TTSResponse:
        """Archive (soft-delete) a TTS job.

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

        Returns:
            TTSResponse: Archived job details

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

        logger.debug("Archiving TTS job: %s", job_id)

        url = urljoin(self._client.base_url, f"tts/{job_id}")
        headers = self._prepare_headers(extra_headers)

        response_data = self._make_request("DELETE", url, headers)
        if not response_data:
            return TTSResponse(
                job_id=job_id,
                status="archived",
                created_at="",
                modified_at="",
            )
        if not isinstance(response_data, dict):
            raise TypeError("Expected dict response from API")
        return TTSResponse(**response_data)

    def count_jobs(
        self,
        from_date: str | None = None,
        until_date: str | None = None,
        query_text: str | None = None,
        extra_headers: dict[str, str] | None = None,
    ) -> int:
        """Count TTS jobs matching the query.

        Args:
            from_date (str | None): Filter jobs from this date (YYYY-MM-DD)
            until_date (str | None): Filter jobs until this date (YYYY-MM-DD)
            query_text (str | None): Search for jobs with matching request text
            extra_headers (dict[str, str] | None): Additional headers

        Returns:
            int: Count of jobs matching the criteria

        Raises:
            httpx.HTTPStatusError: If the API request fails
        """
        logger.debug("Counting TTS jobs")

        url = urljoin(self._client.base_url, "tts/count")
        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
        if query_text is not None:
            params["query_text"] = query_text

        result = self._make_request("GET", url, headers, params=params)
        if not isinstance(result, int):
            raise TypeError("Expected int response from API")
        return result
