use super::super::*;
use super::helpers::*;

#[test]
fn shift_enter_inserts_newline() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);

    session.input_manager.set_content("queued".to_string());

    let result = session.process_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::SHIFT));
    assert!(result.is_none());
    assert_eq!(session.input_manager.content(), "queued\n");
    assert_eq!(
        session.input_manager.cursor(),
        session.input_manager.content().len()
    );
}

#[test]
fn paste_preserves_all_newlines() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
    let pasted = (0..15)
        .map(|i| format!("line {}", i))
        .collect::<Vec<_>>()
        .join("\n");
    let (tx, _rx) = mpsc::unbounded_channel();

    session.handle_event(CrosstermEvent::Paste(pasted.clone()), &tx, None);

    assert_eq!(session.input_manager.content(), pasted);
}

#[test]
fn pasted_message_displays_full_content() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);

    let line_total = ui::INLINE_PASTE_COLLAPSE_LINE_THRESHOLD + 1;
    let pasted_lines: Vec<String> = (1..=line_total).map(|idx| format!("paste-{idx}")).collect();
    let pasted_text = pasted_lines.join("\n");

    session.append_pasted_message(
        InlineMessageKind::User,
        pasted_text.clone(),
        pasted_lines.len(),
    );

    let user_line = session
        .lines
        .iter()
        .find(|line| line.kind == InlineMessageKind::User)
        .expect("user line should exist");
    let combined: String = user_line
        .segments
        .iter()
        .map(|segment| segment.text.as_str())
        .collect();
    assert!(combined.contains("paste-1"));
    assert!(session.collapsed_pastes.is_empty());
}

#[test]
fn pasted_message_collapses_large_json_for_tool() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);

    let mut json = String::from("{\n");
    let line_total = ui::INLINE_JSON_COLLAPSE_LINE_THRESHOLD + 5;
    for idx in 0..line_total {
        json.push_str(&format!("  \"key{idx}\": \"value{idx}\",\n"));
    }
    json.push_str("  \"end\": true\n}");
    let line_count = json.lines().count();

    session.append_pasted_message(InlineMessageKind::Tool, json.clone(), line_count);

    assert_eq!(session.collapsed_pastes.len(), 1);
    let collapsed_index = session.collapsed_pastes[0].line_index;
    let preview_line = session
        .lines
        .get(collapsed_index)
        .expect("collapsed line exists");
    let preview_text: String = preview_line
        .segments
        .iter()
        .map(|segment| segment.text.as_str())
        .collect();
    assert!(preview_text.contains("showing last"));
    assert!(preview_text.contains("\"end\": true"));

    assert!(session.expand_collapsed_paste_at_line_index(collapsed_index));
    assert!(session.collapsed_pastes.is_empty());

    let expanded_line = session
        .lines
        .get(collapsed_index)
        .expect("expanded line exists");
    let expanded_text: String = expanded_line
        .segments
        .iter()
        .map(|segment| segment.text.as_str())
        .collect();
    assert!(expanded_text.contains("\"key0\": \"value0\""));
    assert!(expanded_text.contains("\"end\": true"));
}

#[test]
fn input_compact_preview_for_large_paste() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
    let line_total = ui::INLINE_PASTE_COLLAPSE_LINE_THRESHOLD + 1;
    let pasted_lines: Vec<String> = (1..=line_total).map(|idx| format!("line-{idx}")).collect();
    let pasted_text = pasted_lines.join("\n");

    session.insert_paste_text(&pasted_text);

    let data = session.build_input_widget_data(VIEW_WIDTH, VIEW_ROWS);
    let rendered = text_content(&data.text);
    assert!(rendered.contains("[Pasted Content"));
}

#[test]
fn idle_enter_submits_immediately() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);

    session.set_input("queued".to_string());

    let event = session.process_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
    assert!(matches!(event, Some(InlineEvent::Submit(value)) if value == "queued"));
}

#[test]
fn control_enter_submits_current_draft_immediately() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);

    session.set_input("process now".to_string());

    let event = session.process_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::CONTROL));
    assert!(matches!(event, Some(InlineEvent::Submit(value)) if value == "process now"));
}

#[test]
fn idle_control_enter_with_empty_input_processes_latest_queued_message() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
    session.push_queued_input("first queued".to_string());
    session.push_queued_input("latest queued".to_string());

    let event = session.process_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::CONTROL));
    assert!(matches!(event, Some(InlineEvent::ProcessLatestQueued)));
}

#[test]
fn control_l_submits_clear_command() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
    let event = session.process_key(KeyEvent::new(KeyCode::Char('l'), KeyModifiers::CONTROL));
    assert!(matches!(event, Some(InlineEvent::Submit(value)) if value == "/clear"));
}

#[test]
fn control_slash_toggles_inline_list_visibility() {
    let mut session = AppSession::new(InlineTheme::default(), None, VIEW_ROWS);
    assert!(session.inline_lists_visible());

    let _ = session.process_key(KeyEvent::new(KeyCode::Char('/'), KeyModifiers::CONTROL));
    assert!(!session.inline_lists_visible());

    let _ = session.process_key(KeyEvent::new(KeyCode::Char('/'), KeyModifiers::CONTROL));
    assert!(session.inline_lists_visible());
}

#[test]
fn control_i_toggles_inline_list_visibility() {
    let mut session = AppSession::new(InlineTheme::default(), None, VIEW_ROWS);
    assert!(session.inline_lists_visible());

    let _ = session.process_key(KeyEvent::new(KeyCode::Char('i'), KeyModifiers::CONTROL));
    assert!(!session.inline_lists_visible());

    let _ = session.process_key(KeyEvent::new(KeyCode::Char('i'), KeyModifiers::CONTROL));
    assert!(session.inline_lists_visible());
}

#[test]
fn tab_queues_submission() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);

    session.set_input("queued".to_string());

    let queued = session.process_key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE));
    assert!(matches!(queued, Some(InlineEvent::QueueSubmit(value)) if value == "queued"));
}

#[test]
fn busy_escape_interrupts_then_exits() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
    session.handle_command(InlineCommand::SetInputStatus {
        left: Some("Running command: test".to_string()),
        right: None,
    });

    let first = session.process_key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
    assert!(matches!(first, Some(InlineEvent::Interrupt)));

    let second = session.process_key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
    assert!(matches!(second, Some(InlineEvent::Exit)));
}

#[test]
fn busy_stop_command_interrupts_immediately() {
    let mut session = AppSession::new(InlineTheme::default(), None, VIEW_ROWS);
    set_app_session_busy_status(&mut session);
    session.core.set_input("/stop".to_string());

    let event = session.process_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
    assert!(matches!(event, Some(app_types::InlineEvent::Interrupt)));
}

#[test]
fn busy_pause_command_emits_pause_event() {
    let mut session = AppSession::new(InlineTheme::default(), None, VIEW_ROWS);
    set_app_session_busy_status(&mut session);
    session.core.set_input("/pause".to_string());

    let event = session.process_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
    assert!(matches!(event, Some(app_types::InlineEvent::Pause)));
}

#[test]
fn busy_resume_command_emits_resume_event() {
    let mut session = AppSession::new(InlineTheme::default(), None, VIEW_ROWS);
    set_app_session_busy_status(&mut session);
    session.core.set_input("/resume".to_string());

    let event = session.process_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
    assert!(matches!(event, Some(app_types::InlineEvent::Resume)));
}

#[test]
fn double_escape_submits_rewind_when_idle() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);

    let _ = session.process_key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
    let second = session.process_key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));

    assert!(matches!(second, Some(InlineEvent::Submit(value)) if value == "/rewind"));
}

#[test]
fn alt_up_edits_latest_queued_input() {
    with_terminal_env(None, Some("xterm-256color"), || {
        let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);

        set_queued_inputs(
            &mut session,
            vec!["first".to_string(), "second".to_string()],
        );

        let event = session.process_key(KeyEvent::new(KeyCode::Up, KeyModifiers::ALT));
        assert!(matches!(event, Some(InlineEvent::EditQueue)));
        assert_eq!(session.input_manager.content(), "second");
    });
}

#[test]
fn shift_left_edits_latest_queued_input_in_tmux() {
    with_terminal_env(
        Some("/tmp/tmux-1000/default,123,0"),
        Some("tmux-256color"),
        || {
            let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);

            set_queued_inputs(
                &mut session,
                vec!["first".to_string(), "second".to_string()],
            );

            let event = session.process_key(KeyEvent::new(KeyCode::Left, KeyModifiers::SHIFT));
            assert!(matches!(event, Some(InlineEvent::EditQueue)));
            assert_eq!(session.input_manager.content(), "second");
        },
    );
}

#[test]
fn app_session_shift_left_edits_latest_queued_input_in_tmux() {
    with_terminal_env(
        Some("/tmp/tmux-1000/default,123,0"),
        Some("tmux-256color"),
        || {
            let mut session = AppSession::new(InlineTheme::default(), None, VIEW_ROWS);

            set_app_session_queued_inputs(
                &mut session,
                vec!["first".to_string(), "second".to_string()],
            );

            let event = session.process_key(KeyEvent::new(KeyCode::Left, KeyModifiers::SHIFT));
            assert!(matches!(event, Some(app_types::InlineEvent::EditQueue)));
            assert_eq!(session.core.input_manager.content(), "second");
        },
    );
}

#[test]
fn consecutive_duplicate_submissions_not_stored_twice() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);

    session.set_input("repeat".to_string());
    let first = session.process_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
    assert!(matches!(first, Some(InlineEvent::Submit(value)) if value == "repeat"));

    session.set_input("repeat".to_string());
    let second = session.process_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
    assert!(matches!(second, Some(InlineEvent::Submit(value)) if value == "repeat"));

    assert_eq!(session.input_manager.history().len(), 1);
}

fn queue_edit_hint() -> String {
    if cfg!(target_os = "macos") {
        "\u{2325} + \u{2191} edit".to_string()
    } else {
        "Alt + \u{2191} edit".to_string()
    }
}

fn tmux_queue_edit_hint() -> String {
    if cfg!(target_os = "macos") {
        "\u{21E7} + \u{2190} edit".to_string()
    } else {
        "Shift + \u{2190} edit".to_string()
    }
}

#[test]
fn queued_inputs_overlay_bottom_rows() {
    with_terminal_env(None, Some("xterm-256color"), || {
        let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
        set_queued_inputs(
            &mut session,
            vec![
                "first queued message".to_string(),
                "second queued message".to_string(),
                "third queued message".to_string(),
            ],
        );

        assert_footer_contains(&mut session, 10, "\u{21B3} third queued message");
        assert_footer_contains(&mut session, 10, "\u{21B3} second queued message");
        assert_footer_contains(&mut session, 10, &queue_edit_hint());
    });
}

#[test]
fn queued_inputs_overlay_shows_shift_left_hint_in_tmux() {
    with_terminal_env(
        Some("/tmp/tmux-1000/default,123,0"),
        Some("tmux-256color"),
        || {
            let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
            set_queued_inputs(
                &mut session,
                vec![
                    "first queued message".to_string(),
                    "second queued message".to_string(),
                ],
            );

            assert_footer_contains(&mut session, 10, &tmux_queue_edit_hint());
        },
    );
}

#[test]
fn running_activity_not_overlaid_above_queue_lines() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
    set_queued_inputs(
        &mut session,
        vec![
            "first queued message".to_string(),
            "second queued message".to_string(),
        ],
    );
    session.handle_command(InlineCommand::SetInputStatus {
        left: Some("Running command: test".to_string()),
        right: None,
    });

    let mut visible = vec![TranscriptLine::default(); 6];
    session.overlay_queue_lines(&mut visible, VIEW_WIDTH);
    let rendered: Vec<String> = visible.iter().map(|line| line_text(&line.line)).collect();

    assert!(
        !rendered
            .iter()
            .any(|line| line.contains("Running command: test")),
        "running status should not be overlaid in transcript"
    );
    assert!(
        rendered
            .iter()
            .any(|line| line.contains("\u{21B3} second queued message")),
        "latest queued message should remain visible"
    );
    assert!(
        rendered
            .iter()
            .any(|line| line.contains("\u{21B3} first queued message")),
        "older queued message should remain visible"
    );
}

#[test]
fn running_activity_not_overlaid_without_queue() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
    session.handle_command(InlineCommand::SetInputStatus {
        left: Some("Running tool: grep".to_string()),
        right: None,
    });

    let mut visible = vec![TranscriptLine::default(); 3];
    session.overlay_queue_lines(&mut visible, VIEW_WIDTH);
    let rendered: Vec<String> = visible.iter().map(|line| line_text(&line.line)).collect();

    assert!(
        !rendered
            .iter()
            .any(|line| line.contains("Running tool: grep")),
        "running status should render only in bottom input status row"
    );
}

#[test]
fn apply_suggested_prompt_replaces_empty_input() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);

    session.apply_suggested_prompt("Review the latest diff.".to_string());

    assert_eq!(session.input_manager.content(), "Review the latest diff.");
    assert!(session.suggested_prompt_state.active);
}

#[test]
fn apply_suggested_prompt_appends_to_existing_input() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
    session.set_input("Initial draft".to_string());

    session.apply_suggested_prompt("Review the latest diff.".to_string());

    assert_eq!(
        session.input_manager.content(),
        "Initial draft\n\nReview the latest diff."
    );
    assert!(session.suggested_prompt_state.active);
}

#[test]
fn suggested_prompt_state_clears_after_manual_edit() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
    session.apply_suggested_prompt("Review the latest diff.".to_string());

    session.insert_char('!');

    assert!(!session.suggested_prompt_state.active);
}

#[test]
fn alt_p_requests_inline_prompt_suggestion() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
    session.set_input("Review the current".to_string());

    let event = session.process_key(KeyEvent::new(KeyCode::Char('p'), KeyModifiers::ALT));

    assert!(matches!(
        event,
        Some(InlineEvent::RequestInlinePromptSuggestion(ref value))
            if value == "Review the current"
    ));
}

#[test]
fn tab_accepts_visible_inline_prompt_suggestion() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
    session.set_input("Review the current".to_string());
    session.set_inline_prompt_suggestion("Review the current.diff".to_string(), true);

    let event = session.process_key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE));

    assert!(event.is_none());
    assert_eq!(session.input_manager.content(), "Review the current.diff");
    assert!(session.inline_prompt_suggestion.suggestion.is_none());
}

#[test]
fn tab_accepts_inline_prompt_suggestion_with_trailing_space_prefix() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
    session.set_input("Review the current.diff ".to_string());
    session
        .set_inline_prompt_suggestion("Review the current.diff and summarize it".to_string(), true);

    let event = session.process_key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE));

    assert!(event.is_none());
    assert_eq!(
        session.input_manager.content(),
        "Review the current.diff and summarize it"
    );
    assert!(session.inline_prompt_suggestion.suggestion.is_none());
}

#[test]
fn tab_queues_when_no_inline_prompt_suggestion_is_visible() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
    session.set_input("Review the current.diff".to_string());

    let event = session.process_key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE));

    assert!(matches!(
        event,
        Some(InlineEvent::QueueSubmit(ref value)) if value == "Review the current.diff"
    ));
}

#[test]
fn inline_prompt_suggestion_clears_after_cursor_movement() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
    session.set_input("Review the current".to_string());
    session.set_inline_prompt_suggestion("Review the current.diff".to_string(), false);

    let event = session.process_key(KeyEvent::new(KeyCode::Left, KeyModifiers::NONE));

    assert!(event.is_none());
    assert!(session.inline_prompt_suggestion.suggestion.is_none());
}

#[test]
fn streaming_state_set_on_agent_append_pasted_message() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
    assert!(!session.is_streaming_final_answer);

    session.handle_command(InlineCommand::AppendPastedMessage {
        kind: InlineMessageKind::Agent,
        text: "Hello".to_string(),
        line_count: 1,
    });

    assert!(session.is_streaming_final_answer);
}

#[test]
fn busy_enter_queues_submission() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
    set_busy_status(&mut session);
    session.set_input("keep searching in docs/".to_string());

    let event = session.process_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
    assert!(
        matches!(event, Some(InlineEvent::QueueSubmit(value)) if value == "keep searching in docs/")
    );
}

#[test]
fn busy_control_enter_steers_active_run() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
    set_busy_status(&mut session);
    session.set_input("keep searching in docs/".to_string());

    let event = session.process_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::CONTROL));
    assert!(matches!(event, Some(InlineEvent::Steer(value)) if value == "keep searching in docs/"));
}

#[test]
fn busy_tab_still_queues_submission() {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
    set_busy_status(&mut session);
    session.set_input("queue this next".to_string());

    let event = session.process_key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE));
    assert!(matches!(event, Some(InlineEvent::QueueSubmit(value)) if value == "queue this next"));
}
