"""Assemblage audio final : concat -> normalisation -> WAV -> MP3 taggue.

Pas de pydub (casse en Python 3.13) : concat/normalisation en numpy, encodage
mp3 + cover via ffmpeg CLI, tags via les metadonnees ffmpeg.
"""
from __future__ import annotations

import shutil
import subprocess
from pathlib import Path
from typing import Optional

import numpy as np
import soundfile as sf

from ..settings import get_settings


def _resample(audio: np.ndarray, src_sr: int, dst_sr: int) -> np.ndarray:
    if src_sr == dst_sr or audio.size == 0:
        return audio
    duration = audio.size / src_sr
    n_dst = int(round(duration * dst_sr))
    x_src = np.linspace(0.0, duration, num=audio.size, endpoint=False)
    x_dst = np.linspace(0.0, duration, num=n_dst, endpoint=False)
    return np.interp(x_dst, x_src, audio).astype(np.float32)


def silence(seconds: float, sr: int) -> np.ndarray:
    return np.zeros(int(seconds * sr), dtype=np.float32)


def concat_segments(
    parts: list[tuple[np.ndarray, int]],
    *,
    target_sr: Optional[int] = None,
    gap_seconds: float = 0.35,
    intra_gap_seconds: float = 0.12,
    glued: Optional[list[bool]] = None,
) -> tuple[np.ndarray, int]:
    """Concatene des segments (audio, sr) avec un silence entre chacun.

    `glued[i] == True` (ex: une incise et sa replique, issues du meme paragraphe)
    insere un silence court `intra_gap_seconds` au lieu de `gap_seconds`.
    """
    if target_sr is None:
        target_sr = get_settings().target_sample_rate
    gap = silence(gap_seconds, target_sr)
    intra_gap = silence(intra_gap_seconds, target_sr)
    buf: list[np.ndarray] = []
    first = True
    for i, (audio, sr) in enumerate(parts):
        if audio is None or audio.size == 0:
            continue
        if not first:
            use_intra = glued is not None and i < len(glued) and glued[i]
            buf.append(intra_gap if use_intra else gap)
        first = False
        buf.append(_resample(np.asarray(audio, dtype=np.float32), sr, target_sr))
    if not buf:
        return np.zeros(0, dtype=np.float32), target_sr
    return np.concatenate(buf), target_sr


def normalize_loudness(audio: np.ndarray, target_dbfs: Optional[float] = None) -> np.ndarray:
    """Normalise le niveau RMS vers target_dbfs, avec garde anti-saturation."""
    if audio.size == 0:
        return audio
    if target_dbfs is None:
        target_dbfs = get_settings().target_dbfs
    rms = float(np.sqrt(np.mean(audio.astype(np.float64) ** 2)))
    if rms < 1e-6:
        return audio
    current_dbfs = 20.0 * np.log10(rms)
    gain = 10.0 ** ((target_dbfs - current_dbfs) / 20.0)
    out = audio * gain
    peak = float(np.max(np.abs(out))) if out.size else 0.0
    if peak > 0.99:  # limiteur simple pour eviter le clipping
        out *= 0.99 / peak
    return out.astype(np.float32)


def write_wav(path: str | Path, audio: np.ndarray, sr: int) -> Path:
    path = Path(path)
    path.parent.mkdir(parents=True, exist_ok=True)
    sf.write(str(path), audio, sr)
    return path


def encode_mp3(
    wav_path: str | Path,
    mp3_path: str | Path,
    *,
    bitrate: Optional[str] = None,
    title: Optional[str] = None,
    album: Optional[str] = None,
    artist: Optional[str] = None,
    track: Optional[int] = None,
    cover_path: Optional[str | Path] = None,
) -> Path:
    """Encode un WAV en MP3 (ffmpeg) avec tags ID3 et cover optionnelle."""
    if bitrate is None:
        bitrate = get_settings().mp3_bitrate
    if not shutil.which("ffmpeg"):
        raise RuntimeError("ffmpeg introuvable — brew install ffmpeg")
    wav_path, mp3_path = Path(wav_path), Path(mp3_path)
    mp3_path.parent.mkdir(parents=True, exist_ok=True)

    cmd = ["ffmpeg", "-y", "-i", str(wav_path)]
    has_cover = cover_path and Path(cover_path).exists()
    if has_cover:
        cmd += ["-i", str(cover_path), "-map", "0:a", "-map", "1:v",
                "-c:v", "mjpeg", "-disposition:v", "attached_pic"]
    cmd += ["-c:a", "libmp3lame", "-b:a", bitrate]

    meta = {"title": title, "album": album, "artist": artist}
    if track is not None:
        meta["track"] = str(track)
    for key, val in meta.items():
        if val:
            cmd += ["-metadata", f"{key}={val}"]
    cmd += ["-id3v2_version", "3", str(mp3_path)]

    subprocess.run(cmd, check=True, capture_output=True)
    return mp3_path
