"""Soniox API client for transcription."""

import time
from pathlib import Path
from typing import Callable

import httpx

from .config import Settings, get_settings
from .models import Token, TranscriptionContext, TranscriptionResult


class SonioxError(Exception):
    """Base exception for Soniox API errors."""

    pass


class SonioxClient:
    """Client for Soniox Speech-to-Text API."""

    def __init__(self, settings: Settings | None = None):
        self.settings = settings or get_settings()
        if not self.settings.soniox_api_key:
            raise SonioxError(
                "Missing SONIOX_API_KEY.\n"
                "1. Get your API key at https://console.soniox.com\n"
                "2. Set environment variable: export SONIOX_API_KEY=<YOUR_API_KEY>"
            )
        self._client = httpx.Client(
            base_url=self.settings.soniox_api_base_url,
            headers={"Authorization": f"Bearer {self.settings.soniox_api_key}"},
            timeout=httpx.Timeout(60.0, write=600.0),  # 10 min write timeout for large uploads
        )

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.close()

    def close(self):
        self._client.close()

    def upload_file(self, file_path: Path | str) -> str:
        """Upload an audio file and return the file ID."""
        file_path = Path(file_path)
        if not file_path.exists():
            raise SonioxError(f"File not found: {file_path}")

        with open(file_path, "rb") as f:
            response = self._client.post(
                "/v1/files",
                files={"file": (file_path.name, f)},
            )
        self._check_response(response)
        return response.json()["id"]

    def create_transcription(
        self,
        *,
        file_id: str | None = None,
        audio_url: str | None = None,
        model: str | None = None,
        language_hints: list[str] | None = None,
        enable_speaker_diarization: bool = False,
        enable_language_identification: bool = False,
        context: TranscriptionContext | dict | None = None,
        translation: str | dict | None = None,
    ) -> str:
        """Create a transcription job and return the transcription ID."""
        if not file_id and not audio_url:
            raise SonioxError("Either file_id or audio_url must be provided")

        payload: dict = {"model": model or self.settings.default_model}
        if file_id:
            payload["file_id"] = file_id
        if audio_url:
            payload["audio_url"] = audio_url
        if language_hints:
            payload["language_hints"] = language_hints
        if enable_speaker_diarization:
            payload["enable_speaker_diarization"] = True
        if enable_language_identification:
            payload["enable_language_identification"] = True
        if context:
            if isinstance(context, TranscriptionContext):
                payload["context"] = context.to_dict()
            else:
                payload["context"] = context
        if translation:
            payload["translation"] = translation

        response = self._client.post("/v1/transcriptions", json=payload)
        self._check_response(response)
        return response.json()["id"]

    def get_transcription_status(self, transcription_id: str) -> str:
        """Get the status of a transcription job."""
        response = self._client.get(f"/v1/transcriptions/{transcription_id}")
        self._check_response(response)
        return response.json()["status"]

    def wait_for_completion(
        self,
        transcription_id: str,
        *,
        poll_interval: float | None = None,
        max_attempts: int | None = None,
        on_progress: Callable | None = None,
    ) -> str:
        """Wait for a transcription job to complete."""
        interval = poll_interval or self.settings.poll_interval
        max_tries = max_attempts or self.settings.max_poll_attempts

        for attempt in range(max_tries):
            status = self.get_transcription_status(transcription_id)
            if on_progress:
                on_progress(status, attempt + 1)
            if status == "completed":
                return status
            if status == "error":
                raise SonioxError(f"Transcription failed: {transcription_id}")
            time.sleep(interval)

        raise SonioxError(f"Transcription timed out after {max_tries} attempts")

    def get_transcript(self, transcription_id: str) -> TranscriptionResult:
        """Get the transcript for a completed transcription."""
        response = self._client.get(f"/v1/transcriptions/{transcription_id}/transcript")
        self._check_response(response)
        data = response.json()

        tokens = [
            Token(
                text=t["text"],
                start_ms=t["start_ms"],
                end_ms=t["end_ms"],
                confidence=t.get("confidence", 1.0),
                speaker=t.get("speaker"),
                language=t.get("language"),
            )
            for t in data.get("tokens", [])
        ]

        return TranscriptionResult(
            id=data["id"],
            text=data.get("text", ""),
            tokens=tokens,
        )

    def delete_transcription(self, transcription_id: str) -> None:
        """Delete a transcription job."""
        response = self._client.delete(f"/v1/transcriptions/{transcription_id}")
        self._check_response(response)

    def delete_file(self, file_id: str) -> None:
        """Delete an uploaded file."""
        response = self._client.delete(f"/v1/files/{file_id}")
        self._check_response(response)

    def transcribe(
        self,
        file_path: Path | str | None = None,
        audio_url: str | None = None,
        *,
        model: str | None = None,
        language_hints: list[str] | None = None,
        enable_speaker_diarization: bool = False,
        enable_language_identification: bool = False,
        context: TranscriptionContext | dict | None = None,
        translation: str | dict | None = None,
        cleanup: bool = True,
        on_progress: Callable | None = None,
    ) -> TranscriptionResult:
        """
        Transcribe an audio file or URL.

        This is a convenience method that handles the full workflow:
        1. Upload file (if local path provided)
        2. Create transcription job
        3. Wait for completion
        4. Get transcript
        5. Cleanup (optional)
        """
        file_id = None
        transcription_id = None

        try:
            if file_path:
                file_id = self.upload_file(file_path)

            transcription_id = self.create_transcription(
                file_id=file_id,
                audio_url=audio_url,
                model=model,
                language_hints=language_hints,
                enable_speaker_diarization=enable_speaker_diarization,
                enable_language_identification=enable_language_identification,
                context=context,
                translation=translation,
            )

            self.wait_for_completion(transcription_id, on_progress=on_progress)
            result = self.get_transcript(transcription_id)
            return result

        finally:
            if cleanup:
                if transcription_id:
                    try:
                        self.delete_transcription(transcription_id)
                    except Exception:
                        pass
                if file_id:
                    try:
                        self.delete_file(file_id)
                    except Exception:
                        pass

    def _check_response(self, response: httpx.Response) -> None:
        """Check response for errors."""
        if response.status_code >= 400:
            try:
                error = response.json()
                message = error.get("message", response.text)
            except Exception:
                message = response.text
            raise SonioxError(f"API error ({response.status_code}): {message}")
