"""Audio transcribe command implementation."""

import mimetypes
import sys
from pathlib import Path
from typing import Literal, Protocol, cast

import typer
from alloy_runtime_types.dtos.audio import (
    AudioTranscriptionJSONOutput,
    AudioTranscriptionStatusResponse,
    AudioTranscriptionSubmitResponse,
)

from cli.infrastructure.command import async_command, authenticated_client
from cli.infrastructure.output import OutputService
from cli.commands.audio.transcribe.presenter import present_transcription

MAX_FILE_SIZE_BYTES = 25 * 1024 * 1024  # 25 MB
DEFAULT_POLL_TIMEOUT_SECONDS = 300.0


class AudioTranscriptionClient(Protocol):
    async def submit_audio_transcription(
        self,
        *args: object,
        **kwargs: object,
    ) -> AudioTranscriptionSubmitResponse: ...

    async def get_audio_transcription(
        self,
        transcription_id: str,
    ) -> AudioTranscriptionStatusResponse: ...

    async def wait_for_audio_transcription(
        self,
        transcription_id: str,
        **kwargs: object,
    ) -> AudioTranscriptionStatusResponse: ...

    async def get_audio_transcription_output(
        self,
        transcription_id: str,
        *,
        format: Literal["text", "json", "srt", "vtt"],
    ) -> AudioTranscriptionJSONOutput | str: ...


def resolve_audio_input(file: str) -> tuple[bytes, str, str]:
    """Validate and read audio input from a file path or stdin.

    Args:
        file: File path to an audio file, or '-' for stdin.

    Returns:
        Tuple of (file_bytes, filename, content_type).

    Raises:
        typer.BadParameter: If the file is not found, not readable,
            or exceeds the 25MB size limit.
    """
    if file == "-":
        return _read_stdin()

    return _read_file(file)


def _read_stdin() -> tuple[bytes, str, str]:
    """Read audio bytes from stdin.

    Returns:
        Tuple of (file_bytes, filename, content_type).

    Raises:
        typer.BadParameter: If stdin data exceeds size limit.
    """
    file_bytes = sys.stdin.buffer.read()
    if len(file_bytes) > MAX_FILE_SIZE_BYTES:
        size_mb = len(file_bytes) / (1024 * 1024)
        raise typer.BadParameter(
            f"Stdin data ({size_mb:.1f} MB) exceeds the 25 MB upload limit."
        )
    return file_bytes, "audio.wav", "audio/wav"


def _read_file(file: str) -> tuple[bytes, str, str]:
    """Read and validate an audio file from disk.

    Args:
        file: Path to the audio file.

    Returns:
        Tuple of (file_bytes, filename, content_type).

    Raises:
        typer.BadParameter: If the file is not found, not a regular file,
            or exceeds the 25MB size limit.
    """
    path = Path(file).expanduser()

    if not path.exists():
        raise typer.BadParameter(f"File not found: {file}")

    if not path.is_file():
        raise typer.BadParameter(f"Not a file: {file}")

    file_size = path.stat().st_size
    if file_size > MAX_FILE_SIZE_BYTES:
        size_mb = file_size / (1024 * 1024)
        raise typer.BadParameter(
            f"File ({size_mb:.1f} MB) exceeds the 25 MB upload limit."
        )

    file_bytes = path.read_bytes()
    filename = path.name
    content_type = _detect_content_type(path)

    return file_bytes, filename, content_type


def _detect_content_type(path: Path) -> str:
    """Detect the MIME content type from the file extension.

    Args:
        path: Path to the file.

    Returns:
        MIME type string. Defaults to 'audio/wav' if detection fails.
    """
    guessed, _ = mimetypes.guess_type(str(path))
    if guessed and guessed.startswith("audio/"):
        return guessed
    return "audio/wav"


def audio_transcribe_command(
    file: str | None = typer.Argument(
        None,
        help="Path to audio file, or '-' for stdin.",
    ),
    backend: Literal["local", "elevenlabs"] = typer.Option(
        ..., "--backend", help="Transcription backend to use."
    ),
    audio_url: str | None = typer.Option(None, "--audio-url", help="Remote audio URL."),
    diarize: bool | None = typer.Option(
        None,
        "--diarize/--no-diarize",
        help="Explicitly enable or disable diarization.",
    ),
) -> None:
    """Transcribe audio to text.

    Accepts a local audio file or piped stdin. The file must be under 25 MB.
    Content type is auto-detected from the file extension.

    Examples:
        alloy audio transcribe recording.wav
        alloy audio transcribe ~/voice-memo.mp3
        cat audio.wav | alloy audio transcribe -
    """
    if diarize is None:
        raise typer.BadParameter("You must choose --diarize or --no-diarize.")

    if (file is None) == (audio_url is None):
        raise typer.BadParameter(
            "Provide exactly one source input: file or --audio-url."
        )

    if backend == "local" and audio_url is not None:
        raise typer.BadParameter(
            "--audio-url is only supported with --backend elevenlabs."
        )

    if backend == "local" and diarize:
        raise typer.BadParameter(
            "--diarize is only supported with --backend elevenlabs."
        )

    file_bytes: bytes | None = None
    filename: str | None = None
    content_type: str | None = None
    if file is not None:
        file_bytes, filename, content_type = resolve_audio_input(file)

    _execute_transcribe(
        backend=backend,
        diarize=diarize,
        file_bytes=file_bytes,
        filename=filename,
        content_type=content_type,
        audio_url=audio_url,
    )


@async_command
async def _execute_transcribe(
    *,
    backend: Literal["local", "elevenlabs"],
    diarize: bool,
    file_bytes: bytes | None,
    filename: str | None,
    content_type: str | None,
    audio_url: str | None,
) -> None:
    """Execute the transcribe operation.

    Args:
        file_bytes: Raw audio bytes to upload.
        filename: Name of the audio file.
        content_type: MIME content type of the audio.
    """
    async with authenticated_client() as (_config, client):
        typed_client = cast(AudioTranscriptionClient, client)
        submit_response = await typed_client.submit_audio_transcription(
            file_bytes,
            backend=backend,
            diarize=diarize,
            filename=filename,
            content_type=content_type,
            audio_url=audio_url,
        )

        if submit_response.status == "completed":
            status = await typed_client.get_audio_transcription(
                str(submit_response.transcription_id)
            )
        else:
            status = await typed_client.wait_for_audio_transcription(
                str(submit_response.transcription_id),
                timeout_seconds=DEFAULT_POLL_TIMEOUT_SECONDS,
            )

        if status.status == "completed":
            output = await typed_client.get_audio_transcription_output(
                str(submit_response.transcription_id),
                format="text",
            )
            present_transcription(cast(str, output))
        else:
            OutputService.get().result(
                action="transcribe",
                resource="audio",
                status="failed",
                identifier=str(submit_response.transcription_id),
                name=filename or audio_url,
                extra_fields={"Reason": status.error_message},
            )
            raise typer.Exit(code=1)
