#![allow(clippy::unwrap_used, clippy::expect_used)]

use capo_agent::{Command, UiEvent};
use capo_tui::state::AppState;
use std::time::Duration;
use tokio::sync::mpsc;
use tokio_stream::wrappers::ReceiverStream;

/// Ensures the runtime consumes the UI stream and emits expected commands.
/// Because `run_tui` takes over the real terminal, we exercise `drive` via
/// the re-exported test-only entry point added in this task.
#[tokio::test]
async fn drive_processes_ui_events_without_hanging() {
    let (ui_tx, ui_rx) = mpsc::channel::<UiEvent>(16);
    let ui_stream = ReceiverStream::new(ui_rx);
    let (cmd_tx, mut cmd_rx) = mpsc::channel::<Command>(16);

    let state = AppState::default();
    let handle =
        tokio::spawn(
            async move { capo_tui::runtime::drive_headless(state, ui_stream, cmd_tx).await },
        );

    ui_tx.send(UiEvent::AgentTurnStarted).await.unwrap();
    ui_tx
        .send(UiEvent::AgentTextDelta("hello".into()))
        .await
        .unwrap();
    ui_tx
        .send(UiEvent::AgentMessageComplete("hello".into()))
        .await
        .unwrap();
    ui_tx.send(UiEvent::AgentTurnComplete).await.unwrap();
    drop(ui_tx);

    let final_state = tokio::time::timeout(Duration::from_secs(2), handle)
        .await
        .expect("drive timed out")
        .expect("drive task panicked")
        .expect("drive returned error");

    assert_eq!(final_state.messages.len(), 1);
    // No commands should be emitted for pure UiEvent-driven path.
    assert!(cmd_rx.try_recv().is_err());
}

/// End-to-end regression: drive a long streaming response through the runtime,
/// then render the final state to a small terminal and confirm the *bottom*
/// of the transcript is what shows on screen (auto-follow scroll).
///
/// Pre-fix: the Paragraph was rendered without scroll, so for any transcript
/// taller than the visible area the user saw the oldest lines and never the
/// reply. Post-fix: `messages::render` applies `Paragraph::scroll((y, 0))`
/// anchored to the bottom.
#[tokio::test]
async fn long_streaming_response_auto_follows_to_latest_line() {
    use ratatui::backend::TestBackend;
    use ratatui::Terminal;

    let (ui_tx, ui_rx) = mpsc::channel::<UiEvent>(64);
    let ui_stream = ReceiverStream::new(ui_rx);
    let (cmd_tx, _cmd_rx) = mpsc::channel::<Command>(16);

    let state = AppState::default();
    let handle =
        tokio::spawn(
            async move { capo_tui::runtime::drive_headless(state, ui_stream, cmd_tx).await },
        );

    // Simulate a realistic agent turn that produces ~30 lines of output —
    // far more than fits in a normal terminal's messages pane.
    ui_tx.send(UiEvent::AgentTurnStarted).await.unwrap();
    ui_tx.send(UiEvent::AgentThinking).await.unwrap();
    let long_response = (1..=30)
        .map(|i| format!("response-line-{i:02}"))
        .collect::<Vec<_>>()
        .join("\n");
    ui_tx
        .send(UiEvent::AgentMessageComplete(long_response.clone()))
        .await
        .unwrap();
    ui_tx.send(UiEvent::AgentTurnComplete).await.unwrap();
    drop(ui_tx);

    let final_state = tokio::time::timeout(Duration::from_secs(2), handle)
        .await
        .expect("drive timed out")
        .expect("drive task panicked")
        .expect("drive returned error");

    // Render to a realistic 80x12 terminal — messages pane gets ~8 rows
    // after editor (3) + footer (1) take their share.
    let mut term = Terminal::new(TestBackend::new(80, 12)).expect("terminal init");
    term.draw(|f| capo_tui::render::render(&final_state, f))
        .expect("draw");
    let buf = term.backend().buffer();
    let dumped = (0..buf.area.height)
        .map(|y| {
            (0..buf.area.width)
                .map(|x| buf[(x, y)].symbol())
                .collect::<String>()
        })
        .collect::<Vec<_>>()
        .join("\n");

    assert!(
        dumped.contains("response-line-30"),
        "latest response line must be visible — auto-follow broke. Got:\n{dumped}"
    );
    assert!(
        !dumped.contains("response-line-01"),
        "oldest response line must scroll off in a 12-row terminal with 30 response lines. Got:\n{dumped}"
    );
}
