"""Free / offline text-to-speech fallbacks.

Two providers, ordered most-useful-first:

1. ``gtts``    — Google Translate TTS. Free and surprisingly natural, but
                requires outbound network access. We try this first when
                the paid provider is exhausted.

2. ``silent``  — A pre-encoded silent MP3 (~1 second). Used as a last
                resort so that downstream consumers (notably the video
                composer) still receive valid audio bytes and don't crash.

The shape of the return dict matches what ``PollyService.synthesize``
returns (``audio``, ``content_type``, ``voice``, ``characters``) so
callers can swap providers transparently.
"""

from __future__ import annotations

import io
import logging
import struct

_logger = logging.getLogger(__name__)

try:  # pragma: no cover — gTTS is a soft dep
    from gtts import gTTS
except ImportError:
    gTTS = None


def _build_silent_wav(duration_seconds: float = 1.0,
                      sample_rate: int = 8000) -> bytes:
    """Construct a valid PCM WAV byte string with all-zero samples.

    WAV is trivially constructable from primitives so we can produce it
    without any external library. ffmpeg accepts WAV as readily as MP3,
    so the rest of the pipeline is unaffected by the format choice.
    """
    n_samples = max(1, int(duration_seconds * sample_rate))
    samples = b'\x00\x00' * n_samples  # 16-bit mono silence
    data_size = len(samples)
    fmt_chunk = (
        b'fmt '
        + struct.pack('<I', 16)             # PCM fmt chunk size
        + struct.pack('<H', 1)              # PCM
        + struct.pack('<H', 1)              # mono
        + struct.pack('<I', sample_rate)
        + struct.pack('<I', sample_rate * 2)  # byte rate
        + struct.pack('<H', 2)              # block align
        + struct.pack('<H', 16)             # bits per sample
    )
    data_chunk = b'data' + struct.pack('<I', data_size) + samples
    return (
        b'RIFF'
        + struct.pack('<I', 36 + data_size)
        + b'WAVE'
        + fmt_chunk
        + data_chunk
    )


# Map our internal language codes to (gTTS lang, gTTS tld) tuples. The
# tld controls accent (co.uk vs com vs com.au) so picking carefully here
# gives the listening exam a more authentic accent.
_GTTS_LANG_MAP = {
    'en-GB': ('en', 'co.uk'),
    'en-US': ('en', 'com'),
    'en-AU': ('en', 'com.au'),
    'en-IN': ('en', 'co.in'),
    'en':    ('en', 'co.uk'),
    'ar':    ('ar', 'com'),
    'ar-EG': ('ar', 'com'),
    'ar-SA': ('ar', 'com'),
    'fr':    ('fr', 'fr'),
    'fr-FR': ('fr', 'fr'),
    'es':    ('es', 'es'),
    'de':    ('de', 'de'),
    'tr':    ('tr', 'com'),
    'fa':    ('fa', 'com'),
    'ur':    ('ur', 'com'),
    'hi':    ('hi', 'co.in'),
    'zh':    ('zh-CN', 'com'),
    'ja':    ('ja', 'com'),
}


def synthesize_with_gtts(text, *, language='en-GB'):
    """Synthesize ``text`` to MP3 bytes using gTTS.

    Raises ``RuntimeError`` if gTTS is not installed; the caller is
    expected to catch and try the next provider in the chain.
    """
    if gTTS is None:
        raise RuntimeError(
            'gTTS not installed — pip install gTTS to enable the free '
            'audio fallback.'
        )
    short = (text or '')[:4500]
    if not short.strip():
        return synthesize_silent()
    lang, tld = _GTTS_LANG_MAP.get(language, ('en', 'co.uk'))
    buf = io.BytesIO()
    tts = gTTS(text=short, lang=lang, tld=tld, slow=False)
    tts.write_to_fp(buf)
    return {
        'audio': buf.getvalue(),
        'content_type': 'audio/mpeg',
        'voice': f'gtts-{lang}-{tld}',
        'characters': len(short),
    }


def synthesize_silent(duration_seconds=1):
    """Return a minimal valid silent audio stub.

    Returns a PCM WAV (which ffmpeg accepts identically to MP3) of the
    requested duration. Used when even gTTS is unreachable and we just
    need *some* valid audio so the video composer doesn't fail and the
    media row can still be marked ``ready``.
    """
    payload = _build_silent_wav(duration_seconds=duration_seconds)
    return {
        'audio': payload,
        'content_type': 'audio/wav',
        'voice': 'silent-stub',
        'characters': 0,
    }
