from __future__ import annotations

import argparse
import signal
from pathlib import Path

from .config import ReaderConfig
from .pipeline import StreamingReader
from .speech import (
    ConsoleSpeaker,
    OPENAI_TTS_INSTRUCTIONS,
    OPENAI_TTS_MODEL,
    OPENAI_TTS_RESPONSE_FORMAT,
    OPENAI_TTS_VOICE,
    build_speaker,
)


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="read-docs",
        description=(
            "Stream document narration with smart chunking so playback can start early "
            "while later sections load in the background."
        ),
    )
    parser.add_argument("source", type=Path, help="Path to .pdf, .docx, .txt, or .md file")
    parser.add_argument(
        "--mode",
        choices=("smart", "full"),
        default="smart",
        help="smart = speak key ideas, full = speak cleaned source text",
    )
    parser.add_argument(
        "--style",
        choices=("concise", "balanced", "detailed"),
        default="balanced",
        help="Controls how much detail smart mode includes",
    )
    parser.add_argument("--rate", type=int, default=180, help="Speech rate in words per minute")
    parser.add_argument(
        "--rate-control-file",
        type=Path,
        default=None,
        help="Optional JSON or text file checked between chunks for live speech rate changes",
    )
    parser.add_argument(
        "--voice",
        default=None,
        help="Optional voice substring to select a specific local TTS voice",
    )
    parser.add_argument(
        "--speech-backend",
        choices=(
            "auto",
            "tailscale-4090",
            "tailscale-chatterbox",
            "tailscale-kokoro",
            "local-kokoro",
            "http-tts",
            "openai",
            "macsay",
            "pyttsx3",
        ),
        default="auto",
        help=(
            "Speech backend: auto (default), tailscale-4090, tailscale-chatterbox, "
            "tailscale-kokoro, local-kokoro, http-tts, openai, macsay, or pyttsx3."
        ),
    )
    parser.add_argument(
        "--http-tts-url",
        default=None,
        help="HTTP TTS base URL for --speech-backend http-tts",
    )
    parser.add_argument(
        "--http-tts-engine",
        default=None,
        help="HTTP TTS engine for --speech-backend http-tts",
    )
    parser.add_argument(
        "--http-tts-voice",
        default=None,
        help="Optional HTTP TTS voice name",
    )
    parser.add_argument(
        "--openai-api-key",
        default=None,
        help="Optional OpenAI API key (or set OPENAI_API_KEY / ORP openai-primary)",
    )
    parser.add_argument(
        "--openai-model",
        choices=("gpt-4o-mini-tts", "tts-1", "tts-1-hd"),
        default=OPENAI_TTS_MODEL,
        help=f"OpenAI speech model (default: {OPENAI_TTS_MODEL})",
    )
    parser.add_argument(
        "--openai-voice",
        default=OPENAI_TTS_VOICE,
        help=f"OpenAI voice (default: {OPENAI_TTS_VOICE})",
    )
    parser.add_argument(
        "--openai-response-format",
        choices=("mp3", "opus", "aac", "flac", "wav"),
        default=OPENAI_TTS_RESPONSE_FORMAT,
        help=f"OpenAI audio format for playback (default: {OPENAI_TTS_RESPONSE_FORMAT})",
    )
    parser.add_argument(
        "--openai-instructions",
        default=OPENAI_TTS_INSTRUCTIONS,
        help="Optional delivery instructions for gpt-4o-mini-tts",
    )
    parser.add_argument(
        "--first-chunk-words",
        type=int,
        default=110,
        help="Word budget for the first chunk so narration starts quickly",
    )
    parser.add_argument(
        "--chunk-words",
        type=int,
        default=220,
        help="Word budget for subsequent chunks",
    )
    parser.add_argument(
        "--speech-segment-words",
        type=int,
        default=32,
        help="Small spoken segment target so live speed changes apply quickly",
    )
    parser.add_argument(
        "--queue-size",
        type=int,
        default=8,
        help="Prefetch buffer size for prepared chunks",
    )
    parser.add_argument(
        "--max-chunks",
        type=int,
        default=None,
        help="Optional limit for debugging",
    )
    parser.add_argument(
        "--start-chunk-index",
        type=int,
        default=0,
        help="Start at a specific prepared chunk index (exact resume)",
    )
    parser.add_argument(
        "--start-seconds",
        type=float,
        default=0.0,
        help="Approximate playback offset inside the start chunk in seconds",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Print prepared narration instead of speaking",
    )
    parser.add_argument(
        "--verbose",
        action="store_true",
        help="Print startup and compression stats",
    )
    return parser


def main() -> int:
    parser = build_parser()
    args = parser.parse_args()

    source_path = args.source.expanduser().resolve()
    if not source_path.exists() or not source_path.is_file():
        parser.error(f"File not found: {source_path}")

    config = ReaderConfig(
        source_path=source_path,
        mode=args.mode,
        style=args.style,
        speech_rate=args.rate,
        rate_control_path=args.rate_control_file.expanduser() if args.rate_control_file else None,
        voice_hint=args.voice,
        queue_size=args.queue_size,
        first_chunk_words=args.first_chunk_words,
        chunk_words=args.chunk_words,
        speech_segment_words=args.speech_segment_words,
        max_chunks=args.max_chunks,
        start_chunk_index=max(0, int(args.start_chunk_index)),
        start_seconds=max(0.0, float(args.start_seconds)),
        verbose=args.verbose,
    )

    if args.dry_run:
        speaker = ConsoleSpeaker()
    else:
        try:
            speaker = build_speaker(
                rate=config.speech_rate,
                voice_hint=config.voice_hint,
                backend=args.speech_backend,
                openai_api_key=args.openai_api_key,
                openai_model=args.openai_model,
                openai_voice=args.openai_voice,
                openai_response_format=args.openai_response_format,
                openai_instructions=args.openai_instructions,
                http_tts_url=args.http_tts_url,
                http_tts_engine=args.http_tts_engine,
                http_tts_voice=args.http_tts_voice,
            )
        except RuntimeError as exc:
            print(f"[doc-reader] {exc}")
            print("[doc-reader] Falling back to dry-run mode.")
            speaker = ConsoleSpeaker()

    reader = StreamingReader(config)
    interrupted = False

    def _handle_stop_signal(_signum, _frame) -> None:
        raise KeyboardInterrupt

    # Make SIGTERM/SIGINT cooperative so active speech can be interrupted cleanly.
    signal.signal(signal.SIGTERM, _handle_stop_signal)
    signal.signal(signal.SIGINT, _handle_stop_signal)

    try:
        stats = reader.run(speaker)
    except KeyboardInterrupt:
        interrupted = True
        speaker.close()
        stats = None

    if config.verbose and stats is not None:
        print(
            "[doc-reader] startup latency: "
            f"{stats.startup_latency_seconds:.2f}s | chunks: {stats.chunks_spoken} | "
            f"source words: {stats.source_words} | spoken words: {stats.spoken_words}"
        )
        if stats.source_words:
            ratio = stats.spoken_words / stats.source_words
            print(f"[doc-reader] spoken/source ratio: {ratio:.2f}")

    if interrupted:
        print("[doc-reader] Stopped.")
        return 130

    return 0
