//! Smoke test for the macOS Apple-Speech providers.
//!
//! Exercises (with no permissions required):
//! 1. `AppleSpeechSpeaker::synth("...")` — TTS produces a non-empty WAV
//! 2. `AppleSpeechSpeaker::speak("...")` — TTS plays through default output
//!
//! Skips STT on principle: SFSpeechRecognizer requires
//! - `NSSpeechRecognitionUsageDescription` in the host bundle's Info.plist
//! - the user to have granted Speech Recognition permission via System Settings
//! Both are bundle-level concerns; a CLI binary can't acquire them at runtime.
//! When called from a properly-bundled host app, the same provider works
//! against the same APIs — we cover the error path in the README's
//! authorization section.
//!
//! Run from the repo root:
//!     cd car-rs && cargo run --release -p car-voice --example apple_speech_smoke

#[cfg(target_os = "macos")]
#[tokio::main]
async fn main() {
    use car_voice::tts::Speaker;
    use car_voice::AppleSpeechSpeaker;

    // Pre-flight: bare-metal AVSpeechSynthesizer.speak() to confirm
    // the synthesizer itself works on this host before we exercise
    // the buffer-callback path.
    println!("=== AppleSpeech preflight: speakUtterance + sleep ===");
    bare_speak_test();
    println!();

    // List a few installed voices so we can pick a compatible one.
    list_voices();
    println!();

    println!("=== AppleSpeech TTS smoke test ===\n");

    // Use a compact (offline) voice — `writeUtterance:toBufferCallback:`
    // is silent for premium/enhanced voices that ship as on-demand
    // downloads, only fires for fully-offline voices.
    let mut config = car_voice::VoiceConfig::default();
    config.local_tts_voice = "com.apple.voice.compact.en-US.Samantha".to_string();
    let speaker = AppleSpeechSpeaker::from_config(&config);
    let text = "Hello from the Common Agent Runtime. This is a smoke test \
                of Apple's built-in speech synthesizer.";

    println!("1. synth() — capture WAV via say(1) subprocess");
    let started = std::time::Instant::now();
    let audio = match speaker.synth(text).await {
        Ok(a) => a,
        Err(e) => {
            eprintln!("   FAILED: {}", e);
            std::process::exit(1);
        }
    };
    let ms = started.elapsed().as_millis();
    println!(
        "   captured {} bytes of {:?} in {} ms",
        audio.bytes.len(),
        audio.format,
        ms
    );
    if audio.bytes.len() < 1024 {
        eprintln!("   suspiciously small — expected at least a few KB of WAV");
        std::process::exit(1);
    }

    // Verify WAV header.
    let wav_magic = &audio.bytes[..4];
    if wav_magic != b"RIFF" {
        eprintln!(
            "   WAV magic mismatch: got {:?}",
            std::str::from_utf8(wav_magic).unwrap_or("<non-utf8>")
        );
        std::process::exit(1);
    }
    println!("   RIFF header OK");

    println!();
    println!("2. speak() — play through default output device");
    println!("   (you should hear the line spoken aloud)");
    let started = std::time::Instant::now();
    if let Err(e) = speaker.speak(text).await {
        eprintln!("   FAILED: {}", e);
        std::process::exit(1);
    }
    println!(
        "   playback completed in {} ms",
        started.elapsed().as_millis()
    );

    println!("\nAppleSpeech TTS paths green.");
}

#[cfg(target_os = "macos")]
fn list_voices() {
    use objc2_avf_audio::AVSpeechSynthesisVoice;
    println!("=== Installed voices (first 8) ===");
    let voices = unsafe { AVSpeechSynthesisVoice::speechVoices() };
    for (i, v) in voices.iter().take(8).enumerate() {
        let id = unsafe { v.identifier() }.to_string();
        let name = unsafe { v.name() }.to_string();
        let quality = unsafe { v.quality() };
        let lang = unsafe { v.language() }.to_string();
        println!(
            "   [{}] {} ({}) lang={} quality={:?}",
            i, name, id, lang, quality
        );
    }
    println!("   total voices: {}", voices.len());
}

#[cfg(target_os = "macos")]
fn bare_speak_test() {
    use objc2::rc::Retained;
    use objc2_avf_audio::{AVSpeechSynthesizer, AVSpeechUtterance};
    use objc2_foundation::NSString;

    let synth: Retained<AVSpeechSynthesizer> = unsafe { AVSpeechSynthesizer::new() };
    let utterance: Retained<AVSpeechUtterance> = unsafe {
        let s = NSString::from_str("Preflight check.");
        AVSpeechUtterance::speechUtteranceWithString(&s)
    };
    println!("   isSpeaking before: {}", unsafe { synth.isSpeaking() });
    unsafe { synth.speakUtterance(&utterance) };
    println!("   isSpeaking after speakUtterance: {}", unsafe {
        synth.isSpeaking()
    });
    // Poll briefly for state changes.
    for i in 0..30 {
        std::thread::sleep(std::time::Duration::from_millis(100));
        let speaking = unsafe { synth.isSpeaking() };
        if !speaking && i > 0 {
            println!("   stopped speaking at t={} ms", (i + 1) * 100);
            break;
        }
        if i == 29 {
            println!(
                "   still speaking={} after 3 s — synthesizer is alive",
                speaking
            );
        }
    }
}

#[cfg(not(target_os = "macos"))]
fn main() {
    eprintln!("Apple-Speech providers are macOS only.");
    std::process::exit(2);
}
