"""AudioRunner — modality `audio_t2a` (CP-8 real implementation).

Replaces the CP-4 stub. Delegates to
`recoil.execution.providers.elevenlabs.synthesize_speech` for the actual
TTS call. Translates the runner payload into adapter kwargs, runs the
adapter, adapts the SynthesisResult into a RunResult.

Payload schema:
    {
        "shot_id": str (required),
        "text": str (required),
        "voice_id": str (required),
        "model": str (required, e.g. "eleven_multilingual_v2"),
        "output_format": Optional[str] (default "mp3"),
        "stability": Optional[float],
        "similarity_boost": Optional[float],
        "style": Optional[float],
        "use_speaker_boost": Optional[bool],
        "output_dir": Optional[Path | str] (default $RECOIL_ROOT/_audio_outputs),
        "file_stem": Optional[str] (default f"{shot_id}_audio"),
        "max_retries": Optional[int] (default 3),
        "timeout_s": Optional[float] (default 60.0),
        "_transport": Optional[Callable] (test-only injection).
    }

Class name + zero-arg constructor preserved from CP-4 stub so the
dispatch.py + runners/__init__.py registration sites are byte-unchanged.
"""

from __future__ import annotations

import logging
from pathlib import Path

from recoil.core.paths import RECOIL_ROOT
from recoil.execution.providers import elevenlabs as _eleven
from recoil.pipeline.core.registry import (
    MODALITY_AUDIO_T2A,
    RunResult,
)
from recoil.pipeline.core.runners._shared import (
    _failure_metadata_audio as _failure_metadata,
    make_run_result_id,
)

logger = logging.getLogger(__name__)


class AudioRunner:
    """ModalityRunner for `audio_t2a` — text-to-speech via ElevenLabs.

    Stateless after construction (zero-arg). Each `.run(payload)` call does:
      1. Validate required keys.
      2. Resolve output_dir + file_stem.
      3. Call providers.elevenlabs.synthesize_speech(...).
      4. Adapt SynthesisResult → RunResult.

    Errors from the adapter map to RunResult.success=False; the runner
    NEVER re-raises. This matches the contract of ImageRunner / VideoRunner.
    """

    modality: str = MODALITY_AUDIO_T2A

    def __init__(self) -> None:
        # Zero-arg by design — runner has no per-process state. Matches CP-4 stub.
        pass

    def run(self, payload: dict) -> RunResult:
        shot_id = payload.get("shot_id")
        text = payload.get("text")
        voice_id = payload.get("voice_id")
        model = payload.get("model")

        if not shot_id or not text or not voice_id or not model:
            return RunResult(
                id=make_run_result_id(shot_id, self.modality),
                modality=self.modality,
                success=False,
                error=(
                    f"AudioRunner payload missing required keys: "
                    f"shot_id={shot_id!r}, text={'...' if text else None!r}, "
                    f"voice_id={voice_id!r}, model={model!r}"
                ),
                metadata=_failure_metadata(),
            )

        output_dir_raw = payload.get("output_dir")
        output_dir = (
            Path(output_dir_raw) if output_dir_raw
            else (RECOIL_ROOT / "_audio_outputs")
        )
        file_stem = payload.get("file_stem") or f"{shot_id}_audio"
        output_format = payload.get("output_format", "mp3")

        voice_settings = {
            k: payload[k] for k in
            ("stability", "similarity_boost", "style", "use_speaker_boost")
            if k in payload
        } or None

        try:
            sr = _eleven.synthesize_speech(
                text=text,
                voice_id=voice_id,
                model_id=model,
                output_format=output_format,
                output_dir=output_dir,
                file_stem=file_stem,
                voice_settings=voice_settings,
                timeout_s=payload.get("timeout_s", 60.0),
                max_retries=payload.get("max_retries", 3),
                transport=payload.get("_transport"),
            )
        except _eleven.AudioSynthesisError as e:
            md = _failure_metadata()
            md["error_class"] = type(e).__name__
            return RunResult(
                id=make_run_result_id(shot_id, self.modality),
                modality=self.modality,
                success=False,
                error=f"{type(e).__name__}: {e}",
                metadata=md,
            )
        except Exception as e:  # noqa: BLE001
            md = _failure_metadata()
            md["error_class"] = type(e).__name__
            return RunResult(
                id=make_run_result_id(shot_id, self.modality),
                modality=self.modality,
                success=False,
                error=f"{type(e).__name__}: {e}",
                metadata=md,
            )

        return RunResult(
            id=make_run_result_id(shot_id, self.modality),
            modality=self.modality,
            output_path=str(sr.output_path),
            output_url=None,
            success=True,
            error=None,
            metadata={
                "final_state": "succeeded",
                "cost_usd": float(sr.cost_usd),
                "model": sr.model,
                "voice_id": sr.voice_id,
                "request_id": sr.request_id,
                "char_count": sr.raw_metadata.get("char_count", len(text)),
                "output_format": output_format,
                "duration_s": sr.duration_s,
            },
        )


__all__ = ["AudioRunner"]
