"""Audio stitcher for Tea Party — pydub-based concatenation with log-normal pauses.

Combines per-turn MP3 audio segments into a single MP3 file with
context-aware, log-normally distributed silence gaps between turns.
Pause durations are deterministic per turn index for reproducible builds.

Grounded in conversational timing research:
- Heldner & Edlund (2010): pause durations are log-normal in conversation
- Stivers et al. (2009): responses to questions have shorter latencies
"""

from __future__ import annotations

import math
import os
import random
from io import BytesIO

from alice_cli.errors import AliceCLIError, DependencyError

# Log-normal shape parameter (log-space standard deviation).
_SIGMA_LOG = 0.4

# Clamp bounds as multipliers of the base pause_ms.
_CLAMP_LOW_FACTOR = 0.3
_CLAMP_HIGH_FACTOR = 2.5

# Context-dependent effective median multipliers.
_SAME_SPEAKER_FACTOR = 0.6
_QUESTION_CHANGE_FACTOR = 0.7
_STATEMENT_CHANGE_FACTOR = 1.0


def _compute_pause_ms(
    turn_index: int,
    pause_ms: int,
    same_speaker: bool,
    prev_ends_question: bool,
) -> int:
    """Sample a single inter-turn pause duration in milliseconds.

    Uses a log-normal distribution with a context-dependent effective median,
    seeded deterministically by *turn_index* for reproducibility.
    """
    if pause_ms <= 0:
        return 0

    # 1. Determine effective median.
    if same_speaker:
        effective_median = pause_ms * _SAME_SPEAKER_FACTOR
    elif prev_ends_question:
        effective_median = pause_ms * _QUESTION_CHANGE_FACTOR
    else:
        effective_median = pause_ms * _STATEMENT_CHANGE_FACTOR

    # 2. Sample from log-normal: mu = ln(effective_median), sigma = 0.4.
    mu = math.log(effective_median)
    rng = random.Random(turn_index)
    sample = rng.lognormvariate(mu, _SIGMA_LOG)

    # 3. Clamp to [base × 0.3, base × 2.5] and round.
    lo = pause_ms * _CLAMP_LOW_FACTOR
    hi = pause_ms * _CLAMP_HIGH_FACTOR
    clamped = max(lo, min(hi, sample))
    return round(clamped)


def stitch_audio(
    segments: list[bytes],
    speaker_ids: list[str],
    turn_texts: list[str],
    output_path: str,
    pause_ms: int = 500,
) -> float:
    """Combine MP3 audio segments into a single MP3 file with natural pauses.

    Pauses between turns are sampled from a log-normal distribution where
    *pause_ms* is the median. Uses σ_log = 0.4 for natural right-skewed
    variation, clamped to [base × 0.3, base × 2.5].

    Pause context rules:
    - Same speaker: effective median = pause_ms × 0.6
    - Speaker change after question: effective median = pause_ms × 0.7
    - Speaker change after statement: effective median = pause_ms (full)

    A deterministic seed per turn index ensures reproducible output.

    Args:
        segments: Ordered list of MP3 audio bytes, one per turn.
        speaker_ids: Ordered list of speaker identifiers corresponding to each segment.
        turn_texts: Ordered list of turn dialogue texts (used for question detection).
        output_path: File path for the final stitched MP3.
        pause_ms: Base pause duration in milliseconds (default 500). Treated as the
                  median of the log-normal distribution.

    Returns:
        Total audio duration in seconds.

    Raises:
        DependencyError: If pydub or ffmpeg is not available.
        AliceCLIError: If the output path is not writable.
    """
    # Guarded import per ADR-006.
    try:
        from pydub import AudioSegment
    except ImportError as exc:
        raise DependencyError(
            "Audio stitching needs 'pydub'. Install: pip install alice-cli[audio]"
        ) from exc

    if not segments:
        # Edge case: nothing to stitch — write an empty file.
        try:
            with open(output_path, "wb"):
                pass
        except OSError as exc:
            raise AliceCLIError(f"Cannot write to output path: {output_path}") from exc
        return 0.0

    # Build the combined audio.
    combined = AudioSegment.from_file(BytesIO(segments[0]), format="mp3")

    for i in range(1, len(segments)):
        same_speaker = speaker_ids[i] == speaker_ids[i - 1]
        prev_ends_question = turn_texts[i - 1].rstrip().endswith("?")

        pause_duration = _compute_pause_ms(
            turn_index=i,
            pause_ms=pause_ms,
            same_speaker=same_speaker,
            prev_ends_question=prev_ends_question,
        )

        if pause_duration > 0:
            silence = AudioSegment.silent(duration=pause_duration)
            combined += silence

        segment = AudioSegment.from_file(BytesIO(segments[i]), format="mp3")
        combined += segment

    # Write the final MP3.
    try:
        # Ensure parent directory exists.
        parent = os.path.dirname(output_path)
        if parent:
            os.makedirs(parent, exist_ok=True)
        combined.export(output_path, format="mp3")
    except OSError as exc:
        raise AliceCLIError(f"Cannot write to output path: {output_path}") from exc

    return len(combined) / 1000.0
