//! Regression test: a case whose recordings are all `TranscriptState::Silent`
//! (whisper classified as silence) must produce an `OnelinerState::Empty`
//! automatically, so the UI doesn't stick on "Generating" forever.
//!
//! The historical bug path: whisper wrote a sidecar marking the recording
//! as silent, `update_oneliner` saw no content and returned early without
//! persisting any state, and the UI's `compute_oneliner_display` collapsed
//! the Silent state onto "transcribed → Generating" (missing state). The
//! fix makes `update_oneliner` settle the case to `OnelinerState::Empty`
//! when no content transcript exists and no recording is still Pending.

mod common;

use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;

use doctate_common::oneliners::OnelinerState;
use doctate_server::events;
use doctate_server::gazetteer::Gazetteer;
use doctate_server::{
    AnalyzeBusy, OnelinerHealBusy, PipelineState, TranscribeBusy, WorkerBusy, analyze, transcribe,
};
use tempfile::tempdir;
use wiremock::matchers::{method, path as wm_path};
use wiremock::{Mock, MockServer, ResponseTemplate};

use common::{ONELINER_FILENAME, TestConfig};

/// Seed an m4a plus a silent metadata sidecar — the on-disk shape of
/// a recording that whisper classified as silence.
fn seed_silent_recording(case_dir: &Path, stem: &str) {
    std::fs::write(case_dir.join(format!("{stem}.m4a")), b"audio").unwrap();
    common::seed_recording_meta(case_dir, stem, common::Transcript::Silent, None);
}

#[tokio::test]
async fn silent_only_case_settles_to_empty_without_llm_call() {
    let data = tempdir().unwrap();
    let slug = "dr_test";
    let user_root = data.path().join(slug);
    let case_dir = user_root.join("case-silent");
    std::fs::create_dir_all(&case_dir).unwrap();
    seed_silent_recording(&case_dir, "2026-04-16T10-00-00Z");

    // Ollama must NOT be called — a silent-only case has no content to
    // summarize. `.expect(0)` fails the test (on MockServer drop) if any
    // request arrived.
    let mock = MockServer::start().await;
    Mock::given(method("POST"))
        .and(wm_path("/api/chat"))
        .respond_with(ResponseTemplate::new(200))
        .expect(0)
        .mount(&mock)
        .await;

    let (_config, settings) = TestConfig::new()
        .with_data_path(data.path().to_path_buf())
        .with_ollama(mock.uri())
        .build_pair();

    let vocab = Arc::new(Gazetteer::empty());
    let events_tx = events::channel();
    let http_client = reqwest::Client::new();
    let (tx_a, _rx_a) = analyze::channel();
    let (tx_t, _rx_t) = transcribe::channel();

    let heal_busy: WorkerBusy = Arc::new(AtomicBool::new(false));
    let pipeline = PipelineState {
        analyze_busy: AnalyzeBusy(Arc::new(AtomicBool::new(false))),
        analyze_tx: tx_a,
        transcribe_busy: TranscribeBusy(Arc::new(AtomicBool::new(false))),
        transcribe_tx: tx_t,
        oneliner_heal_busy: OnelinerHealBusy(heal_busy.clone()),
        oneliner_locks: doctate_server::oneliner_locks::OnelinerLocks::new(),
    };

    pipeline
        .heal_orphans_if_idle(
            &user_root,
            slug,
            &http_client,
            &settings,
            &vocab,
            &events_tx,
        )
        .await;

    // Heal spawn drops the busy flag on completion — poll until idle.
    let start = std::time::Instant::now();
    while heal_busy.load(Ordering::Acquire) {
        if start.elapsed() > Duration::from_secs(5) {
            panic!("oneliner heal did not finish within 5s");
        }
        tokio::time::sleep(Duration::from_millis(25)).await;
    }

    // Oneliner is persisted as `Empty`. Without the fix, the file would
    // either not exist or be mid-generation.
    let path = case_dir.join(ONELINER_FILENAME);
    assert!(
        path.exists(),
        "expected oneliner.json to be written for silent-only case"
    );
    let bytes = std::fs::read(&path).unwrap();
    let state: OnelinerState = serde_json::from_slice(&bytes).unwrap();
    assert!(
        matches!(state, OnelinerState::Empty { .. }),
        "expected OnelinerState::Empty, got {state:?}"
    );

    // Mock.drop() runs here — `.expect(0)` panics if Ollama was touched.
}
