"""WAV writing and audio playback utilities."""

import asyncio
import itertools
import time
import wave
from pathlib import Path

import numpy as np

from .settings import settings

_counter = itertools.count(1)


def write_wav(
    samples: np.ndarray,
    sample_rate: int,
    path: Path | None = None,
    prefix: str = "tts-",
) -> Path:
    """Write float32 or int16 samples to a WAV file.

    If path is None, generates a timestamped filename in the output directory.
    Returns the path to the written file.
    """
    if path is None:
        out_dir = settings.output_dir
        out_dir.mkdir(parents=True, exist_ok=True)
        ts = f"{time.strftime('%Y%m%d-%H%M%S')}-{next(_counter):04d}"
        path = out_dir / f"{prefix}{ts}.wav"

    # Normalize float samples to 16-bit PCM
    if samples.dtype in (np.float32, np.float64):
        peak = max(abs(samples.max()), abs(samples.min()), 1e-8)
        samples = (samples / peak * 32767).astype(np.int16)

    with wave.open(str(path), "wb") as wf:
        wf.setnchannels(1)
        wf.setsampwidth(2)
        wf.setframerate(sample_rate)
        wf.writeframes(samples.tobytes())

    return path


def write_wav_from_pcm(
    pcm_bytes: bytes,
    sample_rate: int,
    sample_width: int,
    channels: int,
    path: Path | None = None,
    prefix: str = "tts-",
) -> Path:
    """Write raw PCM bytes to a WAV file."""
    if path is None:
        out_dir = settings.output_dir
        out_dir.mkdir(parents=True, exist_ok=True)
        ts = f"{time.strftime('%Y%m%d-%H%M%S')}-{next(_counter):04d}"
        path = out_dir / f"{prefix}{ts}.wav"

    with wave.open(str(path), "wb") as wf:
        wf.setnchannels(channels)
        wf.setsampwidth(sample_width)
        wf.setframerate(sample_rate)
        wf.writeframes(pcm_bytes)

    return path


def wav_duration(path: Path) -> float:
    """Get duration of a WAV file in seconds."""
    with wave.open(str(path), "rb") as wf:
        return wf.getnframes() / wf.getframerate()


class PlaybackError(RuntimeError):
    """Raised when audio playback fails or times out."""


async def play_audio(path: Path, expected_seconds: float = 0) -> None:
    """Play a WAV file through PipeWire (pw-play). Async wrapper.

    Args:
        path: Path to the WAV file.
        expected_seconds: Expected duration for timeout calculation.
            If 0, reads duration from the WAV file header.
    """
    if expected_seconds <= 0:
        expected_seconds = wav_duration(path)
    # Generous margin: 2x duration + 10s for PipeWire startup overhead
    timeout = max(expected_seconds * 2, 10) + 10

    proc = await asyncio.create_subprocess_exec(
        "pw-play", str(path),
        stdout=asyncio.subprocess.DEVNULL,
        stderr=asyncio.subprocess.PIPE,
    )
    try:
        _, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout)
    except asyncio.TimeoutError:
        proc.kill()
        await proc.wait()
        raise PlaybackError(
            f"pw-play timed out after {timeout:.0f}s "
            f"(expected {expected_seconds:.1f}s audio)"
        )
    except asyncio.CancelledError:
        # Consumer was cancelled (shutdown or cancel) — don't orphan the subprocess
        proc.kill()
        await proc.wait()
        raise
    if proc.returncode != 0:
        raise PlaybackError(
            f"pw-play failed ({proc.returncode}): {stderr.decode().strip()}"
        )
