from __future__ import annotations

import base64
import binascii
from typing import Any, Mapping


class AudioProtocolError(ValueError):
    def __init__(self, code: str, message: str):
        super().__init__(message)
        self.code = code


VAD_LIMITS: dict[str, tuple[type, float, float]] = {
    "threshold": (float, 0.05, 0.95),
    "min_silence_duration_ms": (int, 100, 5000),
    "min_speech_duration_ms": (int, 50, 5000),
    "max_speech_duration_s": (int, 2, 300),
    "speech_pad_ms": (int, 0, 2000),
}


def normalise_vad(values: Mapping[str, Any] | None, defaults: Mapping[str, Any]) -> dict[str, Any]:
    """Return a provider-safe VAD object with strict numeric bounds."""
    result: dict[str, Any] = {"type": "server_vad"}
    supplied = values if isinstance(values, Mapping) else {}
    for key, (coerce, minimum, maximum) in VAD_LIMITS.items():
        raw = supplied.get(key, defaults.get(key, minimum))
        try:
            value = coerce(raw)
        except (TypeError, ValueError):
            value = coerce(defaults.get(key, minimum))
        value = min(maximum, max(minimum, value))
        result[key] = int(value) if coerce is int else float(value)
    return result


def decode_audio_frame(encoded: str, maximum_bytes: int) -> bytes:
    """Decode one strict base64 PCM frame and enforce its post-decode size."""
    if not isinstance(encoded, str) or not encoded:
        raise AudioProtocolError("KIMU_AUDIO_FRAME_INVALID", "The audio frame is empty or invalid.")
    # A base64 string can be roughly 4/3 the decoded size. Reject absurd input before allocation.
    if len(encoded) > ((maximum_bytes + 2) // 3) * 4 + 8:
        raise AudioProtocolError("KIMU_AUDIO_FRAME_TOO_LARGE", "The audio frame exceeds the configured limit.")
    try:
        data = base64.b64decode(encoded, validate=True)
    except (binascii.Error, ValueError) as exc:
        raise AudioProtocolError("KIMU_AUDIO_FRAME_INVALID", "The audio frame is not valid base64.") from exc
    if len(data) > maximum_bytes:
        raise AudioProtocolError("KIMU_AUDIO_FRAME_TOO_LARGE", "The audio frame exceeds the configured limit.")
    if len(data) % 2:
        raise AudioProtocolError("KIMU_AUDIO_FRAME_ALIGNMENT", "PCM16 audio frames must contain an even number of bytes.")
    return data
