use super::super::*;
use super::helpers::*;
use crate::core_tui::session::{input, status_requires_shimmer};
use std::time::Instant;

// ─── Helpers ────────────────────────────────────────────────────────────────

fn fresh_session() -> Session {
    let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
    session.appearance.hide_header = false;
    session
}

fn header_line_text(session: &mut Session) -> String {
    let lines = session.header_lines();
    assert_eq!(lines.len(), 1, "expected single-line header");
    lines[0]
        .spans
        .iter()
        .map(|span| span.content.clone().into_owned())
        .collect()
}

fn assert_header_contains_badge(session: &mut Session, badge_text: &str) {
    let text = header_line_text(session);
    assert!(
        text.contains(badge_text),
        "header should contain badge '{badge_text}', got: {text}"
    );
}

fn assert_badge_style(line: &Line<'_>, badge_text: &str, expected_fg: Color) {
    let badge_span = line
        .spans
        .iter()
        .find(|span| span.content.as_ref() == badge_text)
        .unwrap_or_else(|| panic!("badge span '{badge_text}' not found"));
    assert_eq!(badge_span.style.fg, Some(expected_fg));
    assert!(badge_span.style.add_modifier.contains(Modifier::BOLD));
}

fn setup_shimmer_session(session: &mut Session, left_status: &str) {
    session.handle_command(InlineCommand::SetInputStatus {
        left: Some(left_status.to_string()),
        right: None,
    });
}

fn clear_shimmer_session(session: &mut Session) {
    session.handle_command(InlineCommand::SetInputStatus {
        left: None,
        right: None,
    });
}

fn input_data(session: &mut Session) -> input::InputWidgetData {
    session.build_input_widget_data(VIEW_WIDTH, 1)
}

fn session_with_highlights(highlights: Vec<InlineHeaderHighlight>) -> Session {
    let mut session = fresh_session();
    session.header_context.highlights = highlights;
    session.input_manager.set_content("notes".to_string());
    session
        .input_manager
        .set_cursor(session.input_manager.content().len());
    session
}

#[test]
fn copy_notification_renders_in_input_status_line() {
    let mut session = fresh_session();
    session.show_copy_notification();

    let rendered = session
        .render_input_status_line(VIEW_WIDTH)
        .expect("input status line")
        .spans
        .iter()
        .map(|span| span.content.as_ref())
        .collect::<String>();

    assert!(rendered.contains("Copied to clipboard"));
}

#[test]
fn copy_notification_expires_after_five_seconds() {
    let mut session = fresh_session();
    session.show_copy_notification();
    session.copy_notification_until =
        Some(Instant::now().checked_sub(Duration::from_secs(1)).unwrap());
    session.handle_tick();

    let rendered = session
        .render_input_status_line(VIEW_WIDTH)
        .map(|line| {
            line.spans
                .iter()
                .map(|span| span.content.as_ref())
                .collect::<String>()
        })
        .unwrap_or_default();

    assert!(!rendered.contains("Copied to clipboard"));
}

#[test]
fn cursor_visible_while_scrolling() {
    let mut session = fresh_session();
    let initial = input_data(&mut session);
    assert!(initial.cursor_should_be_visible);

    session.scroll_line_down();
    let during_scroll = input_data(&mut session);
    assert!(during_scroll.cursor_should_be_visible);
    assert!(session.use_steady_cursor());

    session.scroll_cursor_steady_until = Some(
        Instant::now()
            .checked_sub(Duration::from_millis(1))
            .unwrap(),
    );
    session.handle_tick();

    assert!(!session.use_steady_cursor());
    let after_scroll = input_data(&mut session);
    assert!(after_scroll.cursor_should_be_visible);
}

#[test]
fn cursor_steady_during_shimmer() {
    let mut session = fresh_session();
    let initial = input_data(&mut session);
    assert!(initial.cursor_should_be_visible);
    assert!(!session.use_steady_cursor());

    setup_shimmer_session(&mut session, "Running command: test");
    let during_shimmer = input_data(&mut session);
    assert!(during_shimmer.cursor_should_be_visible);
    assert!(session.use_steady_cursor());

    clear_shimmer_session(&mut session);
    assert!(!session.use_steady_cursor());
    let after_shimmer = input_data(&mut session);
    assert!(after_shimmer.cursor_should_be_visible);
}

#[test]
fn cursor_fake_during_status_shimmer() {
    let mut session = fresh_session();
    let initial = input_data(&mut session);
    assert!(initial.cursor_should_be_visible);
    assert!(!initial.use_fake_cursor);

    setup_shimmer_session(&mut session, "Loading (Esc, Ctrl+C, or /stop to stop)");
    let during_shimmer = input_data(&mut session);
    assert!(during_shimmer.cursor_should_be_visible);
    assert!(during_shimmer.use_fake_cursor);
    assert!(session.use_steady_cursor());

    clear_shimmer_session(&mut session);
    let after_shimmer = input_data(&mut session);
    assert!(after_shimmer.cursor_should_be_visible);
    assert!(!after_shimmer.use_fake_cursor);
}

#[test]
fn permission_overlay_surfaces_action_required_status() {
    let mut session = fresh_session();
    session.show_overlay(OverlayRequest::List(ListOverlayRequest {
        title: "Tool Permission Required".to_string(),
        lines: vec!["Need approval".to_string()],
        footer_hint: None,
        items: vec![InlineListItem {
            title: "Approve Once".to_string(),
            subtitle: None,
            badge: None,
            indent: 0,
            selection: Some(InlineListSelection::ToolApproval(true)),
            search_value: None,
        }],
        selected: Some(InlineListSelection::ToolApproval(true)),
        search: None,
        hotkeys: Vec::new(),
    }));

    let rendered = session
        .render_input_status_line(VIEW_WIDTH)
        .expect("input status line")
        .spans
        .iter()
        .map(|span| span.content.as_ref())
        .collect::<String>();

    assert!(rendered.contains("Approval required"));
    assert!(status_requires_shimmer("Approval required"));
}

#[test]
fn non_permission_overlay_surfaces_generic_action_required_status() {
    let mut session = fresh_session();
    session.show_overlay(OverlayRequest::List(ListOverlayRequest {
        title: "Open External Link".to_string(),
        lines: vec!["example.com".to_string()],
        footer_hint: None,
        items: vec![InlineListItem {
            title: "Open".to_string(),
            subtitle: None,
            badge: None,
            indent: 0,
            selection: Some(InlineListSelection::ConfigAction("open".to_string())),
            search_value: None,
        }],
        selected: None,
        search: None,
        hotkeys: Vec::new(),
    }));

    let rendered = session
        .render_input_status_line(VIEW_WIDTH)
        .expect("input status line")
        .spans
        .iter()
        .map(|span| span.content.as_ref())
        .collect::<String>();

    assert!(rendered.contains("Action required"));
    assert!(status_requires_shimmer("Action required"));
}

#[test]
fn status_requires_shimmer_for_input_required_states() {
    assert!(status_requires_shimmer("Approval required"));
    assert!(status_requires_shimmer("Input required"));
    assert!(status_requires_shimmer("Waiting for approval"));
    assert!(status_requires_shimmer("Action required"));
}

#[test]
fn bang_prefix_input_shows_shell_mode_status_hint() {
    let mut session = fresh_session();
    session.set_input("!echo hello".to_string());

    let spans = session
        .build_input_status_widget_data(VIEW_WIDTH)
        .expect("expected shell mode status hint");
    let rendered: String = spans
        .iter()
        .map(|span| span.content.clone().into_owned())
        .collect();

    assert!(rendered.contains("Shell mode (!):"));
}

#[test]
fn bang_prefix_input_enables_shell_mode_border_title() {
    let mut session = fresh_session();
    session.set_input("   !ls -la".to_string());

    assert_eq!(session.shell_mode_border_title(), Some(" ! Shell mode "));
}

#[test]
fn bang_prefix_input_uses_zero_padding_for_visible_input_area() {
    let mut session = fresh_session();
    session.set_input("!echo hello".to_string());

    assert_eq!(
        session.input_block_padding(),
        ratatui::widgets::Padding::new(0, 0, 0, 0)
    );
}

#[test]
fn non_bang_input_has_no_shell_mode_border_title() {
    let mut session = fresh_session();
    session.set_input("run ls -la".to_string());

    assert_eq!(session.shell_mode_border_title(), None);
}

#[test]
fn non_bang_input_uses_default_padding() {
    let mut session = fresh_session();
    session.set_input("run ls -la".to_string());

    assert_eq!(
        session.input_block_padding(),
        ratatui::widgets::Padding::new(
            ui::INLINE_INPUT_PADDING_HORIZONTAL,
            ui::INLINE_INPUT_PADDING_HORIZONTAL,
            ui::INLINE_INPUT_PADDING_VERTICAL,
            ui::INLINE_INPUT_PADDING_VERTICAL,
        )
    );
}

#[test]
fn question_mark_opens_help_overlay_when_input_is_empty() {
    let mut session = fresh_session();

    let result = session.process_key(KeyEvent::new(KeyCode::Char('?'), KeyModifiers::NONE));

    assert!(result.is_none());
    let modal = session.modal_state().expect("help modal should open");
    assert_eq!(modal.title, "Keyboard Shortcuts");
    assert!(
        modal
            .lines
            .iter()
            .any(|line| line.contains("Ctrl+A / Ctrl+E"))
    );
}

#[test]
fn question_mark_inserts_character_when_input_has_content() {
    let mut session = session_with_input("why", 3);

    let result = session.process_key(KeyEvent::new(KeyCode::Char('?'), KeyModifiers::NONE));

    assert!(result.is_none());
    assert_eq!(session.input_manager.content(), "why?");
    assert_eq!(session.cursor(), 4);
    assert!(session.modal_state().is_none());
}

#[test]
fn header_shows_safe_badge_for_tools_policy_trust() {
    let mut session = fresh_session();
    session.header_context.workspace_trust = format!("{}tools policy", ui::HEADER_TRUST_PREFIX);
    session.input_manager.set_content("test".to_string());
    session
        .input_manager
        .set_cursor(session.input_manager.content().len());

    assert_header_contains_badge(&mut session, "Safe");
}

#[test]
fn header_shows_full_auto_trust_badge_for_full_auto_trust() {
    let mut session = fresh_session();
    session.header_context.workspace_trust = format!("{}full auto", ui::HEADER_TRUST_PREFIX);
    session.input_manager.set_content("test".to_string());
    session
        .input_manager
        .set_cursor(session.input_manager.content().len());

    assert_header_contains_badge(&mut session, "Full-auto");
}

#[test]
fn header_shows_auto_badge() {
    let mut session = fresh_session();
    session.header_context.autonomous_mode = true;
    session.header_context.workspace_trust = format!("{}tools policy", ui::HEADER_TRUST_PREFIX);
    session.input_manager.set_content("test".to_string());
    session
        .input_manager
        .set_cursor(session.input_manager.content().len());

    let text = header_line_text(&mut session);
    assert!(text.contains("Auto"));
    assert!(text.contains("Safe"));
}

#[test]
fn header_shows_pr_review_status_badge() {
    let mut session = fresh_session();
    session.header_context.pr_review = Some(InlineHeaderStatusBadge {
        text: "PR: outdated".to_string(),
        tone: InlineHeaderStatusTone::Warning,
    });

    let line = session.header_meta_line();
    assert_badge_style(&line, "PR: outdated", Color::Yellow);
}

#[test]
fn header_shows_persistent_memory_status_badge() {
    let mut session = fresh_session();
    session.header_context.persistent_memory = Some(InlineHeaderStatusBadge {
        text: "Memory: cleanup".to_string(),
        tone: InlineHeaderStatusTone::Warning,
    });

    let line = session.header_meta_line();
    assert_badge_style(&line, "Memory: cleanup", Color::Yellow);
}

#[test]
fn header_meta_line_excludes_editor_context() {
    let mut session = fresh_session();
    session.header_context.editor_context =
        Some("File: src/main.rs · Rust · Sel 120-148".to_string());

    let line = session.header_meta_line();
    let summary = line_text(&line);

    assert!(!summary.contains("File: src/main.rs"));
}

#[test]
fn header_title_line_shows_model_context_window() {
    let mut session = fresh_session();
    session.header_context.provider = format!("{}Anthropic", ui::HEADER_PROVIDER_PREFIX);
    session.header_context.model = format!("{}claude-sonnet-4-6", ui::HEADER_MODEL_PREFIX);
    session.header_context.context_window_size = Some(1_000_000);

    let summary = line_text(&session.header_title_line());
    assert!(summary.contains("claude-sonnet-4-6 (1M)"));

    session.header_context.model = format!("{}claude-haiku-4-5", ui::HEADER_MODEL_PREFIX);
    session.header_context.context_window_size = Some(200_000);

    let summary = line_text(&session.header_title_line());
    assert!(summary.contains("claude-haiku-4-5 (200K)"));
}

#[test]
fn hidden_header_summary_combines_provider_model_and_styles_effort_label() {
    let mut session = fresh_session();
    session.appearance.hide_header = true;
    session.apply_transcript_width(VIEW_WIDTH);
    session.header_context.provider = format!("{}mimo", ui::HEADER_PROVIDER_PREFIX);
    session.header_context.model = format!("{}mimo-v2-flash", ui::HEADER_MODEL_PREFIX);
    session.header_context.reasoning = format!("{}medium", ui::HEADER_REASONING_PREFIX);

    let lines = session.header_lines();
    assert_eq!(lines.len(), 1);

    let line = &lines[0];
    let summary = line_text(line);
    assert!(summary.contains("Mimo Mimo-V2-Flash"));
    assert!(!summary.contains("Mimo · Mimo-V2-Flash"));
    assert!(summary.contains("effort: medium"));

    let effort_label = line
        .spans
        .iter()
        .find(|span| span.content.as_ref() == "effort:")
        .expect("effort label should be rendered");
    assert!(effort_label.style.add_modifier.contains(Modifier::ITALIC));
    assert!(effort_label.style.add_modifier.contains(Modifier::DIM));
}

#[test]
fn hidden_header_summary_live_reloads_mode_changes() {
    let mut session = fresh_session();
    session.appearance.hide_header = true;
    session.apply_transcript_width(VIEW_WIDTH);

    let initial = header_line_text(&mut session);
    assert!(initial.contains("Edit"));

    session.handle_command(InlineCommand::SetAutonomousMode(true));
    let auto = header_line_text(&mut session);
    assert!(auto.contains("Auto"));
    assert!(!auto.contains("Edit"));

    session.handle_command(InlineCommand::SetAutonomousMode(false));
    session.handle_command(InlineCommand::SetEditingMode(
        crate::core_tui::EditingMode::Plan,
    ));
    let plan = header_line_text(&mut session);
    assert!(plan.contains("Plan"));
    assert!(!plan.contains("Auto"));
}

#[test]
fn hidden_header_summary_live_reloads_model_changes() {
    let mut session = fresh_session();
    session.appearance.hide_header = true;
    session.apply_transcript_width(VIEW_WIDTH);
    session.header_context.provider = format!("{}mimo", ui::HEADER_PROVIDER_PREFIX);
    session.header_context.model = format!("{}mimo-v2-flash", ui::HEADER_MODEL_PREFIX);
    session.header_context.reasoning = format!("{}medium", ui::HEADER_REASONING_PREFIX);

    let initial = header_line_text(&mut session);
    assert!(initial.contains("Mimo Mimo-V2-Flash"));

    let mut next_context = session.header_context.clone();
    next_context.provider = format!("{}moonshot", ui::HEADER_PROVIDER_PREFIX);
    next_context.model = format!("{}kimi-k2", ui::HEADER_MODEL_PREFIX);
    next_context.reasoning = format!("{}high", ui::HEADER_REASONING_PREFIX);
    session.handle_command(InlineCommand::SetHeaderContext {
        context: Box::new(next_context),
    });

    let updated = header_line_text(&mut session);
    assert!(updated.contains("Moonshot Kimi-K2"));
    assert!(updated.contains("effort: high"));
    assert!(!updated.contains("Mimo Mimo-V2-Flash"));
}

#[test]
fn header_context_updates_preserve_live_mode_state() {
    let mut session = fresh_session();
    session.appearance.hide_header = true;
    session.apply_transcript_width(VIEW_WIDTH);

    session.handle_command(InlineCommand::SetAutonomousMode(true));
    let mut replacement = session.header_context.clone();
    replacement.provider = format!("{}mimo", ui::HEADER_PROVIDER_PREFIX);
    replacement.model = format!("{}mimo-v2-flash", ui::HEADER_MODEL_PREFIX);
    replacement.reasoning = format!("{}low", ui::HEADER_REASONING_PREFIX);
    replacement.editing_mode = crate::core_tui::EditingMode::Edit;
    replacement.autonomous_mode = false;
    session.handle_command(InlineCommand::SetHeaderContext {
        context: Box::new(replacement),
    });

    let auto = header_line_text(&mut session);
    assert!(auto.contains("Auto"));
    assert!(!auto.contains("Edit ·"));

    session.handle_command(InlineCommand::SetAutonomousMode(false));
    session.handle_command(InlineCommand::SetEditingMode(
        crate::core_tui::EditingMode::Plan,
    ));
    let mut replacement = session.header_context.clone();
    replacement.provider = format!("{}moonshot", ui::HEADER_PROVIDER_PREFIX);
    replacement.model = format!("{}kimi-k2", ui::HEADER_MODEL_PREFIX);
    replacement.reasoning = format!("{}high", ui::HEADER_REASONING_PREFIX);
    replacement.editing_mode = crate::core_tui::EditingMode::Edit;
    replacement.autonomous_mode = false;
    session.handle_command(InlineCommand::SetHeaderContext {
        context: Box::new(replacement),
    });

    let plan = header_line_text(&mut session);
    assert!(plan.contains("Plan"));
    assert!(!plan.contains("Edit ·"));
}

#[test]
fn header_highlights_collapse_to_single_line() {
    let mut session = session_with_highlights(vec![
        InlineHeaderHighlight {
            title: "Keyboard Shortcuts".to_string(),
            lines: vec![
                "/help Show help".to_string(),
                "Enter Submit message".to_string(),
            ],
        },
        InlineHeaderHighlight {
            title: "Usage Tips".to_string(),
            lines: vec!["- Keep tasks focused".to_string()],
        },
    ]);

    let lines = session.header_lines();
    assert_eq!(lines.len(), 1);

    let summary: String = lines[0]
        .spans
        .iter()
        .map(|span| span.content.clone().into_owned())
        .collect();

    assert!(summary.contains("Keyboard Shortcuts"));
    assert!(summary.contains("/help Show help"));
    assert!(summary.contains("(+1 more)"));
    assert!(!summary.contains("Enter Submit message"));
    assert!(summary.contains("Usage Tips"));
    assert!(summary.contains("Keep tasks focused"));
}

#[test]
fn header_highlight_summary_truncates_long_entries() {
    let limit = ui::HEADER_HIGHLIGHT_PREVIEW_MAX_CHARS;
    let long_entry = "A".repeat(limit + 5);
    let mut session = session_with_highlights(vec![InlineHeaderHighlight {
        title: "Details".to_string(),
        lines: vec![long_entry.clone()],
    }]);

    let lines = session.header_lines();
    assert_eq!(lines.len(), 1);

    let summary: String = lines[0]
        .spans
        .iter()
        .map(|span| span.content.clone().into_owned())
        .collect();

    let expected_preview = format!(
        "{}{}",
        "A".repeat(limit.saturating_sub(1)),
        ui::INLINE_PREVIEW_ELLIPSIS
    );

    assert!(summary.contains("Details"));
    assert!(summary.contains(&expected_preview));
    assert!(!summary.contains(&long_entry));
}

#[test]
fn header_highlight_summary_hides_truncated_command_segments() {
    let mut session = session_with_highlights(vec![InlineHeaderHighlight {
        title: String::new(),
        lines: vec![
            "  - /{command}".to_string(),
            "  - /help Show slash command help".to_string(),
            "  - Enter Submit message".to_string(),
            "  - Escape Cancel input".to_string(),
        ],
    }]);

    let lines = session.header_lines();
    assert_eq!(lines.len(), 1);

    let summary: String = lines[0]
        .spans
        .iter()
        .map(|span| span.content.clone().into_owned())
        .collect();

    assert!(summary.contains("/{command}"));
    assert!(summary.contains("(+3 more)"));
    assert!(!summary.contains("Escape"));
    assert!(!summary.contains(ui::INLINE_PREVIEW_ELLIPSIS));
}

#[test]
fn header_height_expands_when_wrapping_required() {
    let mut session = fresh_session();
    session.header_context.provider = format!(
        "{}Example Provider With Extended Label",
        ui::HEADER_PROVIDER_PREFIX
    );
    session.header_context.model = format!(
        "{}ExampleModelIdentifierWithDetail",
        ui::HEADER_MODEL_PREFIX
    );
    session.header_context.reasoning = format!("{}medium", ui::HEADER_REASONING_PREFIX);
    session.header_context.mode = ui::HEADER_MODE_AUTO.to_string();
    session.header_context.workspace_trust = format!("{}full auto", ui::HEADER_TRUST_PREFIX);
    session.header_context.tools = format!(
        "{}allow 11 · prompt 7 · deny 0 · extras extras extras",
        ui::HEADER_TOOLS_PREFIX
    );
    session.header_context.mcp = format!("{}enabled", ui::HEADER_MCP_PREFIX);
    session.header_context.highlights = vec![InlineHeaderHighlight {
        title: "Tips".to_string(),
        lines: vec![
            "- Use /prompt:quick-start for boilerplate".to_string(),
            "- Keep responses focused".to_string(),
        ],
    }];
    session.input_manager.set_content("notes".to_string());
    session
        .input_manager
        .set_cursor(session.input_manager.content().len());

    let wide = session.header_height_for_width(120);
    let narrow = session.header_height_for_width(40);

    assert!(
        narrow >= wide,
        "expected narrower width to require at least as many header rows"
    );
    assert!(
        wide >= ui::INLINE_HEADER_HEIGHT && narrow >= ui::INLINE_HEADER_HEIGHT,
        "expected header rows to meet minimum height"
    );
}

#[test]
fn agent_label_uses_accent_color_without_border() {
    let accent = AnsiColorEnum::Rgb(RgbColor(0x12, 0x34, 0x56));
    let theme = InlineTheme {
        primary: Some(accent),
        ..Default::default()
    };

    let mut session = Session::new(theme, None, VIEW_ROWS);
    session.labels.agent = Some("Agent".to_string());
    let mut segment = make_segment("Response");
    segment.style = Arc::new(InlineTextStyle {
        color: Some(accent),
        ..InlineTextStyle::default()
    });
    session.push_line(InlineMessageKind::Agent, vec![segment]);

    let index = session
        .lines
        .len()
        .checked_sub(1)
        .expect("agent message should be available");
    let spans = session.render_message_spans(index);

    assert!(!spans.is_empty());

    let prefix_span = &spans[0];
    assert_eq!(
        prefix_span.content.clone().into_owned(),
        ui::INLINE_AGENT_QUOTE_PREFIX
    );

    let label_index = spans
        .iter()
        .position(|span| span.content.clone().into_owned() == "Agent")
        .expect("agent label span should be present");
    let label_span = &spans[label_index];
    assert_eq!(label_span.style.fg, Some(Color::Rgb(0x12, 0x34, 0x56)));

    let padding_span = spans
        .get(label_index + 1)
        .expect("agent label should be followed by padding");
    assert_eq!(
        padding_span.content.clone().into_owned(),
        ui::INLINE_AGENT_MESSAGE_LEFT_PADDING
    );

    assert!(
        !spans
            .iter()
            .any(|span| span.content.clone().into_owned().contains('│')),
        "agent prefix should not render a left border",
    );
    assert!(
        !spans
            .iter()
            .any(|span| span.content.clone().into_owned().contains('✦')),
        "agent prefix should not include decorative symbols",
    );
}
