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

This module provides the SpeechToText class for handling STT operations
with the Prosa Speech API, including transcription, job management, and status tracking.

Authors:
    GDP Labs

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

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

import httpx

from gl_speech_sdk.models import (
    STTConfig,
    STTJobRequest,
    STTJobsListResponse,
    STTRequest,
    STTResponse,
    STTStatusResponse,
)

logger = logging.getLogger(__name__)


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

    def __init__(self, client):
        """Initialize SpeechToText 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,
        wait: bool | None = None,
        speaker_count: int | None = None,
        include_filler: bool | None = None,
        include_partial_results: bool | None = None,
        auto_punctuation: bool | None = None,
        enable_spoken_numerals: bool | None = None,
        enable_speech_insights: bool | None = None,
        enable_voice_insights: bool | None = None,
        enable_conversation_analytics: bool | None = None,
        conversation_analytics_prompt: str | None = None,
        label: str | None = None,
        data: str | None = None,
        uri: str | None = None,
        duration: float | None = None,
        mime_type: str | None = None,
        sample_rate: int | None = None,
        channels: int | None = None,
    ) -> dict[str, Any]:
        """Prepare request data for the STT API call.

        Args:
            model (str): ASR model name
            wait (bool | None): Whether to wait for completion
            speaker_count (int | None): Expected number of speakers
            include_filler (bool | None): Include filler words
            include_partial_results (bool | None): Include partial results
            auto_punctuation (bool | None): Auto-add punctuation
            enable_spoken_numerals (bool | None): Convert spoken numerals
            enable_speech_insights (bool | None): Enable speech insights
            enable_voice_insights (bool | None): Enable voice insights
            enable_conversation_analytics (bool | None): Enable conversation analytics
            conversation_analytics_prompt (str | None): Prompt type for conversation analytics
            label (str | None): Job label
            data (str | None): Base64-encoded audio data
            uri (str | None): URI to audio file
            duration (float | None): Audio duration in seconds
            mime_type (str | None): Audio MIME type
            sample_rate (int | None): Audio sample rate
            channels (int | None): Number of audio channels

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

        config = STTConfig(
            model=model,
            wait=wait,
            speaker_count=speaker_count,
            include_filler=include_filler,
            include_partial_results=include_partial_results,
            auto_punctuation=auto_punctuation,
            enable_spoken_numerals=enable_spoken_numerals,
            enable_speech_insights=enable_speech_insights,
            enable_voice_insights=enable_voice_insights,
            enable_conversation_analytics=enable_conversation_analytics,
            conversation_analytics_prompt=conversation_analytics_prompt,
        )

        request = STTRequest(
            label=label,
            data=data,
            uri=uri,
            duration=duration,
            mime_type=mime_type,
            sample_rate=sample_rate,
            channels=channels,
        )

        job_request = STTJobRequest(config=config, request=request)
        return job_request.model_dump(exclude_none=True, by_alias=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]]:
        """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]]: 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)):
                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 ASR models.

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

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

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

        url = urljoin(self._client.base_url, "stt/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 transcribe(
        self,
        model: str,
        wait: bool | None = None,
        speaker_count: int | None = None,
        include_filler: bool | None = None,
        include_partial_results: bool | None = None,
        auto_punctuation: bool | None = None,
        enable_spoken_numerals: bool | None = None,
        enable_speech_insights: bool | None = None,
        enable_voice_insights: bool | None = None,
        enable_conversation_analytics: bool | None = None,
        conversation_analytics_prompt: str | None = None,
        label: str | None = None,
        data: str | None = None,
        uri: str | None = None,
        duration: float | None = None,
        mime_type: str | None = None,
        sample_rate: int | None = None,
        channels: int | None = None,
        extra_headers: dict[str, str] | None = None,
    ) -> STTResponse:
        """Submit a speech-to-text transcription request.

        Args:
            model (str): ASR model name (e.g., "stt-general")
            wait (bool | None): If True, blocks until execution finishes.
                For short audio (<60s), typically set to True.
            speaker_count (int | None): Expected number of speakers
            include_filler (bool | None): Include filler words in result
            include_partial_results (bool | None): Include partial results
            auto_punctuation (bool | None): Automatically add punctuation
            enable_spoken_numerals (bool | None): Convert spoken numerals to digits
            enable_speech_insights (bool | None): Enable speech insight analytics
            enable_voice_insights (bool | None): Enable voice insight analytics
            enable_conversation_analytics (bool | None): Enable conversation analytics
            conversation_analytics_prompt (str | None): Prompt type for conversation analytics
            label (str | None): Optional label for the job
            data (str | None): Base64-encoded audio data. Either data or uri required.
            uri (str | None): URI to audio file. Either data or uri required.
                Supported: https://, googledrive://
            duration (float | None): Audio duration in seconds (for progress reporting)
            mime_type (str | None): Audio MIME type
            sample_rate (int | None): Audio sample rate
            channels (int | None): Number of audio channels
            extra_headers (dict[str, str] | None): Additional headers

        Returns:
            STTResponse: Transcription response with job_id and possibly result

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

        logger.debug("Submitting STT transcription request")

        url = urljoin(self._client.base_url, "stt")
        headers = self._prepare_headers(extra_headers)
        json_data = self._prepare_request_data(
            model=model,
            wait=wait,
            speaker_count=speaker_count,
            include_filler=include_filler,
            include_partial_results=include_partial_results,
            auto_punctuation=auto_punctuation,
            enable_spoken_numerals=enable_spoken_numerals,
            enable_speech_insights=enable_speech_insights,
            enable_voice_insights=enable_voice_insights,
            enable_conversation_analytics=enable_conversation_analytics,
            conversation_analytics_prompt=conversation_analytics_prompt,
            label=label,
            data=data,
            uri=uri,
            duration=duration,
            mime_type=mime_type,
            sample_rate=sample_rate,
            channels=channels,
        )

        response_data = self._make_request("POST", url, headers, json_data)
        if not isinstance(response_data, dict):
            raise TypeError("Expected dict response from API")
        return STTResponse(**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,
    ) -> STTJobsListResponse:
        """List STT 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 result text
            extra_headers (dict[str, str] | None): Additional headers

        Returns:
            STTJobsListResponse: List of STT jobs with pagination

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

        url = urljoin(self._client.base_url, "stt")
        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 STTJobsListResponse(**response_data)

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

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

        Returns:
            STTResponse: 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 STT job: %s", job_id)

        url = urljoin(self._client.base_url, f"stt/{job_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 STTResponse(**response_data)

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

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

        Returns:
            STTStatusResponse: 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 STT job status: %s", job_id)

        url = urljoin(self._client.base_url, f"stt/{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 STTStatusResponse(**response_data)

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

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

        Returns:
            STTResponse: 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 STT job: %s", job_id)

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

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