use ratatui::{
    layout::{Constraint, Direction, Layout, Rect},
    style::{Color, Modifier, Style},
    text::{Line, Span},
    widgets::{
        Block, Borders, Clear, List, ListItem, Paragraph, Scrollbar, ScrollbarOrientation,
        ScrollbarState, Wrap,
    },
    Frame,
};

use super::common::{build_pr_info, truncate_with_width};
use crate::ai::{RallyState, ReviewAction, RevieweeStatus};
use crate::app::{AiRallyState, App, LogEntry, LogEventType, PauseState};
use crate::config::KeybindingsConfig;

pub fn render(frame: &mut Frame, app: &mut App) {
    let pr_info = build_pr_info(app);

    let Some(rally_state) = &mut app.ai_rally_state else {
        return;
    };

    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(4),
            Constraint::Min(10),
            Constraint::Length(3),
        ])
        .split(frame.area());

    let kb = &app.config.keybindings;
    render_header(frame, chunks[0], rally_state, &pr_info);
    render_main_content(frame, chunks[1], rally_state, kb);
    render_status_bar(frame, chunks[2], rally_state, kb);

    if rally_state.showing_log_detail {
        render_log_detail_modal(frame, rally_state);
    }
}

fn render_header(frame: &mut Frame, area: Rect, state: &AiRallyState, pr_info: &str) {
    let base_state_text = match state.state {
        RallyState::Initializing => "Initializing...",
        RallyState::ReviewerReviewing => "Reviewer reviewing...",
        RallyState::RevieweeFix => "Reviewee fixing...",
        RallyState::RevieweeProposing => "Reviewee designing fix proposal...",
        RallyState::WaitingForClarification => "Waiting for clarification",
        RallyState::WaitingForPermission => "Waiting for permission",
        RallyState::WaitingForPostConfirmation => "Waiting for post confirmation",
        RallyState::Completed => "Completed!",
        RallyState::Aborted => "Aborted",
        RallyState::Error => "Error",
    };

    let state_text = match state.pause_state {
        PauseState::PauseRequested => format!("{} (Pausing...)", base_state_text),
        PauseState::Paused => format!("{} (PAUSED)", base_state_text),
        PauseState::Running => base_state_text.to_string(),
    };

    let state_color = if state.pause_state == PauseState::Paused {
        Color::Yellow
    } else {
        match state.state {
            RallyState::Initializing => Color::Blue,
            RallyState::ReviewerReviewing => Color::Yellow,
            RallyState::RevieweeFix | RallyState::RevieweeProposing => Color::Cyan,
            RallyState::WaitingForClarification
            | RallyState::WaitingForPermission
            | RallyState::WaitingForPostConfirmation => Color::Magenta,
            RallyState::Completed => Color::Green,
            RallyState::Aborted => Color::Yellow,
            RallyState::Error => Color::Red,
        }
    };

    let title = if state.review_only {
        // Proposal-iteration mode also has a finite max_iterations; show n/max
        // so the user can track progress like normal mode.
        format!(
            " AI Rally [Review Only] - Iteration {}/{} ",
            state.iteration, state.max_iterations
        )
    } else {
        format!(
            " AI Rally - Iteration {}/{} ",
            state.iteration, state.max_iterations
        )
    };

    let header = Paragraph::new(vec![
        Line::from(Span::styled(pr_info, Style::default().fg(Color::White))),
        Line::from(vec![
            Span::styled("Status: ", Style::default().fg(Color::Gray)),
            Span::styled(
                state_text,
                Style::default()
                    .fg(state_color)
                    .add_modifier(Modifier::BOLD),
            ),
        ]),
    ])
    .block(
        Block::default()
            .borders(Borders::ALL)
            .title(title)
            .border_style(Style::default().fg(state_color)),
    );

    frame.render_widget(header, area);
}

fn render_main_content(
    frame: &mut Frame,
    area: Rect,
    state: &mut AiRallyState,
    kb: &KeybindingsConfig,
) {
    if state.pending_config_warning.is_some() {
        render_config_warning(frame, area, state, kb);
        return;
    }

    let is_waiting = matches!(
        state.state,
        RallyState::WaitingForClarification
            | RallyState::WaitingForPermission
            | RallyState::WaitingForPostConfirmation
    );

    let chunks = if is_waiting {
        Layout::default()
            .direction(Direction::Vertical)
            .constraints([
                Constraint::Percentage(35),
                Constraint::Length(6),
                Constraint::Min(10),
            ])
            .split(area)
    } else {
        Layout::default()
            .direction(Direction::Vertical)
            .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
            .split(area)
    };

    render_history(frame, chunks[0], state);

    if is_waiting {
        render_waiting_prompt(frame, chunks[1], state, kb);
        render_logs(frame, chunks[2], state);
    } else {
        render_logs(frame, chunks[1], state);
    }
}

fn render_config_warning(
    frame: &mut Frame,
    area: Rect,
    state: &AiRallyState,
    kb: &KeybindingsConfig,
) {
    let warnings = match &state.pending_config_warning {
        Some(w) => w,
        None => return,
    };

    let mut lines = vec![
        Line::from(vec![Span::styled(
            "Local .octorus/ overrides detected that affect AI behavior:",
            Style::default()
                .fg(Color::Yellow)
                .add_modifier(Modifier::BOLD),
        )]),
        Line::from(""),
    ];

    for (key, value) in warnings {
        lines.push(Line::from(vec![
            Span::styled(format!("  {}: ", key), Style::default().fg(Color::Red)),
            Span::styled(value.clone(), Style::default().fg(Color::White)),
        ]));
    }

    lines.push(Line::from(""));
    lines.push(Line::from(vec![Span::styled(
        "These overrides could alter AI agent behavior in unexpected ways.",
        Style::default().fg(Color::Yellow),
    )]));
    lines.push(Line::from(""));
    let help_text = format!(
        "Press '{}' to accept and continue, '{}'/'{}' to cancel",
        kb.confirm_yes.display(),
        kb.confirm_no.display(),
        kb.quit.display()
    );
    lines.push(Line::from(vec![Span::styled(
        help_text,
        Style::default()
            .fg(Color::Cyan)
            .add_modifier(Modifier::BOLD),
    )]));

    let warning = Paragraph::new(lines).wrap(Wrap { trim: false }).block(
        Block::default()
            .borders(Borders::ALL)
            .title(" Local Override Security Warning ")
            .border_style(Style::default().fg(Color::Yellow)),
    );

    frame.render_widget(warning, area);
}

fn render_waiting_prompt(
    frame: &mut Frame,
    area: Rect,
    state: &AiRallyState,
    kb: &KeybindingsConfig,
) {
    let yes = kb.confirm_yes.display();
    let no = kb.confirm_no.display();
    let quit = kb.quit.display();
    let (title, content, help) = match state.state {
        RallyState::WaitingForClarification => {
            let question = state
                .pending_question
                .as_deref()
                .unwrap_or("(No question provided)");
            (
                " Clarification Required ",
                format!("Question: {}", question),
                format!(
                    "Press '{}' to open editor and respond, '{}' to skip, '{}' to abort",
                    yes, no, quit
                ),
            )
        }
        RallyState::WaitingForPermission => {
            let (action, reason) = state
                .pending_permission
                .as_ref()
                .map(|p| (p.action.as_str(), p.reason.as_str()))
                .unwrap_or(("(No action)", "(No reason)"));
            (
                " Permission Required ",
                format!("Action: {}\nReason: {}", action, reason),
                format!(
                    "Press '{}' to approve, '{}' to deny, '{}' to abort",
                    yes, no, quit
                ),
            )
        }
        RallyState::WaitingForPostConfirmation => match &state.pending_post_confirmation {
            crate::app::PendingPostConfirmation::Review(info) => {
                let summary = truncate_with_width(&info.summary, 120);
                (
                    " Review Post Confirmation ",
                    format!(
                        "Action: {}\nSummary: {}\nComments: {}",
                        info.action, summary, info.comment_count
                    ),
                    format!(
                        "Press '{}' to post to PR, '{}' to skip, '{}' to abort",
                        yes, no, quit
                    ),
                )
            }
            crate::app::PendingPostConfirmation::Fix(info) => {
                let summary = truncate_with_width(&info.summary, 120);
                let files_display = if info.files_modified.len() <= 5 {
                    info.files_modified.join(", ")
                } else {
                    let shown: Vec<&str> = info
                        .files_modified
                        .iter()
                        .take(5)
                        .map(|s| s.as_str())
                        .collect();
                    format!(
                        "{} (+{} more)",
                        shown.join(", "),
                        info.files_modified.len() - 5
                    )
                };
                (
                    " Fix Post Confirmation ",
                    format!("Summary: {}\nFiles: {}", summary, files_display),
                    format!(
                        "Press '{}' to post to PR, '{}' to skip, '{}' to abort",
                        yes, no, quit
                    ),
                )
            }
            crate::app::PendingPostConfirmation::Proposal(info) => {
                let summary = truncate_with_width(&info.summary, 120);
                let files_display = if info.target_files.len() <= 5 {
                    info.target_files.join(", ")
                } else {
                    let shown: Vec<&str> = info
                        .target_files
                        .iter()
                        .take(5)
                        .map(|s| s.as_str())
                        .collect();
                    format!(
                        "{} (+{} more)",
                        shown.join(", "),
                        info.target_files.len() - 5
                    )
                };
                (
                    " Reviewee Proposal Post Confirmation ",
                    format!(
                        "Summary: {}\nPlan items: {}\nFiles: {}",
                        summary, info.plan_item_count, files_display
                    ),
                    format!(
                        "Press '{}' to post to PR, '{}' to skip, '{}' to abort",
                        yes, no, quit
                    ),
                )
            }
            crate::app::PendingPostConfirmation::None => return,
        },
        _ => return,
    };

    let lines = vec![
        Line::from(vec![Span::styled(
            content,
            Style::default().fg(Color::White),
        )]),
        Line::from(""),
        Line::from(vec![Span::styled(help, Style::default().fg(Color::Yellow))]),
    ];

    let prompt = Paragraph::new(lines).wrap(Wrap { trim: false }).block(
        Block::default()
            .borders(Borders::ALL)
            .title(title)
            .border_style(Style::default().fg(Color::Magenta)),
    );

    frame.render_widget(prompt, area);
}

fn render_history(frame: &mut Frame, area: Rect, state: &AiRallyState) {
    let visible_height = area.height.saturating_sub(2) as usize;

    let items: Vec<ListItem> = state
        .history
        .iter()
        .filter_map(|event| {
            let (prefix, content, color) = match event {
                crate::ai::orchestrator::RallyEvent::RallyStarted { review_only }
                    if *review_only =>
                {
                    (
                        "Mode".to_string(),
                        "Review Only — reviewee (fix) phase will be skipped".to_string(),
                        Color::Cyan,
                    )
                }
                crate::ai::orchestrator::RallyEvent::IterationStarted(i) => (
                    format!("[{}]", i),
                    "Iteration started".to_string(),
                    Color::Blue,
                ),
                crate::ai::orchestrator::RallyEvent::ReviewCompleted(review) => {
                    let action_text = match review.action {
                        ReviewAction::Approve => "APPROVE",
                        ReviewAction::RequestChanges => "REQUEST_CHANGES",
                        ReviewAction::Comment => "COMMENT",
                    };
                    let color = match review.action {
                        ReviewAction::Approve => Color::Green,
                        ReviewAction::RequestChanges => Color::Red,
                        ReviewAction::Comment => Color::Yellow,
                    };
                    (
                        format!("Review: {}", action_text),
                        truncate_with_width(&review.summary, 60).into_owned(),
                        color,
                    )
                }
                crate::ai::orchestrator::RallyEvent::FixCompleted(fix) => {
                    let status_text = match fix.status {
                        RevieweeStatus::Completed => "COMPLETED",
                        RevieweeStatus::NeedsClarification => "NEEDS_CLARIFICATION",
                        RevieweeStatus::NeedsPermission => "NEEDS_PERMISSION",
                        RevieweeStatus::Error => "ERROR",
                    };
                    let color = match fix.status {
                        RevieweeStatus::Completed => Color::Green,
                        RevieweeStatus::NeedsClarification | RevieweeStatus::NeedsPermission => {
                            Color::Yellow
                        }
                        RevieweeStatus::Error => Color::Red,
                    };
                    (
                        format!("Fix: {}", status_text),
                        truncate_with_width(&fix.summary, 60).into_owned(),
                        color,
                    )
                }
                crate::ai::orchestrator::RallyEvent::ClarificationNeeded(q) => (
                    "Clarification".to_string(),
                    truncate_with_width(q, 60).into_owned(),
                    Color::Magenta,
                ),
                crate::ai::orchestrator::RallyEvent::PermissionNeeded(action, _) => (
                    "Permission".to_string(),
                    truncate_with_width(action, 60).into_owned(),
                    Color::Magenta,
                ),
                crate::ai::orchestrator::RallyEvent::Approved(summary) => (
                    "APPROVED".to_string(),
                    truncate_with_width(summary, 60).into_owned(),
                    Color::Green,
                ),
                crate::ai::orchestrator::RallyEvent::ReviewOnlyCompleted(review) => {
                    let action_text = match review.action {
                        ReviewAction::Approve => "APPROVE",
                        ReviewAction::RequestChanges => "REQUEST_CHANGES",
                        ReviewAction::Comment => "COMMENT",
                    };
                    let color = match review.action {
                        ReviewAction::Approve => Color::Green,
                        ReviewAction::RequestChanges => Color::Red,
                        ReviewAction::Comment => Color::Yellow,
                    };
                    (
                        format!("Review Only: {}", action_text),
                        truncate_with_width(&review.summary, 60).into_owned(),
                        color,
                    )
                }
                crate::ai::orchestrator::RallyEvent::Error(e) => (
                    "ERROR".to_string(),
                    truncate_with_width(e, 60).into_owned(),
                    Color::Red,
                ),
                _ => return None,
            };

            Some(ListItem::new(Line::from(vec![
                Span::styled(
                    format!("{}: ", prefix),
                    Style::default().fg(color).add_modifier(Modifier::BOLD),
                ),
                Span::styled(content, Style::default().fg(Color::White)),
            ])))
        })
        .collect();

    let total = items.len();
    let scroll_offset = total.saturating_sub(visible_height);
    let visible_items: Vec<ListItem> = items.into_iter().skip(scroll_offset).collect();

    let list = List::new(visible_items).block(
        Block::default()
            .borders(Borders::ALL)
            .title(" History ")
            .border_style(Style::default().fg(Color::Gray)),
    );

    frame.render_widget(list, area);
}

fn render_logs(frame: &mut Frame, area: Rect, state: &mut AiRallyState) {
    let visible_height = area.height.saturating_sub(2) as usize;
    state.last_visible_log_height = visible_height;
    let total_logs = state.logs.len();

    let scroll_offset = if state.log_scroll_offset == 0 {
        total_logs.saturating_sub(visible_height)
    } else {
        state.log_scroll_offset
    };

    let items: Vec<ListItem> = state
        .logs
        .iter()
        .enumerate()
        .skip(scroll_offset)
        .take(visible_height)
        .map(|(idx, entry)| {
            let is_selected = state.selected_log_index == Some(idx);
            format_log_entry(entry, is_selected)
        })
        .collect();

    let block = Block::default()
        .borders(Borders::ALL)
        .title(format!(
            " Logs ({}/{}) [j/k/↑↓: select, Enter: detail] ",
            scroll_offset.saturating_add(visible_height).min(total_logs),
            total_logs
        ))
        .border_style(Style::default().fg(Color::Gray));

    let inner_area = block.inner(area);
    frame.render_widget(block, area);

    let list = List::new(items);
    frame.render_widget(list, inner_area);

    if total_logs > visible_height {
        let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
            .begin_symbol(Some("▲"))
            .end_symbol(Some("▼"));

        let mut scrollbar_state =
            ScrollbarState::new(total_logs.saturating_sub(visible_height)).position(scroll_offset);

        frame.render_stateful_widget(
            scrollbar,
            area.inner(ratatui::layout::Margin {
                vertical: 1,
                horizontal: 0,
            }),
            &mut scrollbar_state,
        );
    }
}

fn format_log_entry(entry: &LogEntry, is_selected: bool) -> ListItem<'static> {
    let (icon, color) = match entry.event_type {
        LogEventType::Info => ("[i]", Color::Blue),
        LogEventType::Thinking => ("[~]", Color::Magenta),
        LogEventType::ToolUse => ("[>]", Color::Cyan),
        LogEventType::ToolResult => ("[+]", Color::Green),
        LogEventType::Text => ("[.]", Color::White),
        LogEventType::Review => ("[R]", Color::Yellow),
        LogEventType::Fix => ("[F]", Color::Cyan),
        LogEventType::Error => ("[!]", Color::Red),
    };

    let type_label = match entry.event_type {
        LogEventType::Info => "Info",
        LogEventType::Thinking => "Think",
        LogEventType::ToolUse => "Tool",
        LogEventType::ToolResult => "Result",
        LogEventType::Text => "Output",
        LogEventType::Review => "Review",
        LogEventType::Fix => "Fix",
        LogEventType::Error => "Error",
    };

    let selector = if is_selected { ">" } else { " " };

    let display_message = truncate_with_width(&entry.message, 80).into_owned();

    let mut item = ListItem::new(Line::from(vec![
        Span::styled(
            selector.to_string(),
            Style::default()
                .fg(Color::Yellow)
                .add_modifier(Modifier::BOLD),
        ),
        Span::styled(
            format!("[{}] ", entry.timestamp),
            Style::default().fg(Color::DarkGray),
        ),
        Span::styled(format!("{} ", icon), Style::default().fg(color)),
        Span::styled(
            format!("{}: ", type_label),
            Style::default().fg(color).add_modifier(Modifier::BOLD),
        ),
        Span::styled(display_message, Style::default().fg(Color::White)),
    ]));

    if is_selected {
        item = item.style(Style::default().bg(Color::DarkGray));
    }

    item
}

fn render_log_detail_modal(frame: &mut Frame, state: &AiRallyState) {
    let Some(selected_idx) = state.selected_log_index else {
        return;
    };

    let Some(entry) = state.logs.get(selected_idx) else {
        return;
    };

    let area = frame.area();
    let modal_width = (area.width as f32 * 0.8) as u16;
    let modal_height = (area.height as f32 * 0.6) as u16;
    let modal_x = (area.width.saturating_sub(modal_width)) / 2;
    let modal_y = (area.height.saturating_sub(modal_height)) / 2;

    let modal_area = Rect::new(modal_x, modal_y, modal_width, modal_height);

    frame.render_widget(Clear, modal_area);

    let (type_label, color) = match entry.event_type {
        LogEventType::Info => ("Info", Color::Blue),
        LogEventType::Thinking => ("Thinking", Color::Magenta),
        LogEventType::ToolUse => ("Tool Use", Color::Cyan),
        LogEventType::ToolResult => ("Tool Result", Color::Green),
        LogEventType::Text => ("Output", Color::White),
        LogEventType::Review => ("Review", Color::Yellow),
        LogEventType::Fix => ("Fix", Color::Cyan),
        LogEventType::Error => ("Error", Color::Red),
    };

    let title = format!(" {} - {} ", type_label, entry.timestamp);

    let content = Paragraph::new(entry.message.clone())
        .wrap(Wrap { trim: false })
        .style(Style::default().fg(Color::White))
        .block(
            Block::default()
                .borders(Borders::ALL)
                .title(title)
                .title_bottom(Line::from(" Press Esc/Enter/q to close ").centered())
                .border_style(Style::default().fg(color)),
        );

    frame.render_widget(content, modal_area);
}

fn render_status_bar(frame: &mut Frame, area: Rect, state: &AiRallyState, kb: &KeybindingsConfig) {
    let yes = kb.confirm_yes.display();
    let no = kb.confirm_no.display();
    let quit = kb.quit.display();
    let mv = format!("{}/{}/↑↓", kb.move_down.display(), kb.move_up.display());
    let enter = kb.open_panel.display();
    let bg = kb.rally_background.display();
    let pause = kb.rally_pause.display();
    let retry = kb.retry.display();

    let help_text = if state.pending_config_warning.is_some() {
        format!("{yes}: Accept and continue | {no}/{quit}: Cancel and return")
    } else if state.showing_log_detail {
        format!("{quit}/{enter}: Close detail")
    } else if state.pause_state == PauseState::Paused {
        format!(
            "{pause}: Resume | {mv}: select | {enter}: detail | {bg}: Background | {quit}: Abort"
        )
    } else if state.pause_state == PauseState::PauseRequested {
        format!("{pause}: Cancel pause | {mv}: select | {enter}: detail | {bg}: Background | {quit}: Abort")
    } else {
        match state.state {
            RallyState::WaitingForClarification => {
                format!("{yes}: Open editor | {no}: Skip | {mv}: select | {enter}: detail | {quit}: Abort")
            }
            RallyState::WaitingForPermission => {
                format!("{yes}: Approve | {no}: Deny | {mv}: select | {enter}: detail | {quit}: Abort")
            }
            RallyState::WaitingForPostConfirmation => {
                format!("{yes}: Post to PR | {no}: Skip | {mv}: select | {enter}: detail | {quit}: Abort")
            }
            RallyState::Completed => format!("{mv}: select | {enter}: detail | {bg}: Background | {quit}: Close"),
            RallyState::Aborted => format!("{mv}: select | {enter}: detail | {bg}: Background | {quit}: Close"),
            RallyState::Error => {
                format!("{retry}: Retry | {mv}: select | {enter}: detail | {bg}: Background | {quit}: Close")
            }
            _ => format!("{pause}: Pause | {mv}: select | {enter}: detail | {bg}: Background | {quit}: Abort"),
        }
    };

    let status_bar = Paragraph::new(Line::from(vec![Span::styled(
        help_text,
        Style::default().fg(Color::Cyan),
    )]))
    .block(
        Block::default()
            .borders(Borders::ALL)
            .border_style(Style::default().fg(Color::DarkGray)),
    );

    frame.render_widget(status_bar, area);
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ai::RallyState;
    use crate::app::{AiRallyState, App, AppState, LogEntry, LogEventType, PauseState};
    use insta::assert_snapshot;
    use ratatui::backend::TestBackend;
    use ratatui::Terminal;

    fn make_rally_state() -> AiRallyState {
        make_rally_state_with(false)
    }

    fn make_rally_state_with(review_only: bool) -> AiRallyState {
        AiRallyState {
            iteration: 1,
            max_iterations: 3,
            review_only,
            state: RallyState::Initializing,
            history: vec![],
            logs: vec![],
            log_scroll_offset: 0,
            selected_log_index: None,
            showing_log_detail: false,
            pending_question: None,
            pending_permission: None,
            pending_post_confirmation: crate::app::PendingPostConfirmation::None,
            last_visible_log_height: 0,
            pending_config_warning: None,
            pause_state: PauseState::Running,
        }
    }

    fn render_full(app: &mut App) -> String {
        let backend = TestBackend::new(100, 24);
        let mut terminal = Terminal::new(backend).unwrap();
        terminal
            .draw(|frame| {
                render(frame, app);
            })
            .unwrap();
        let buf = terminal.backend().buffer();
        let mut lines = Vec::new();
        for y in 0..24u16 {
            let mut line = String::new();
            for x in 0..100u16 {
                let cell = &buf[(x, y)];
                line.push_str(cell.symbol());
            }
            lines.push(line.trim_end().to_string());
        }
        lines.join("\n")
    }

    #[test]
    fn test_initializing_state() {
        let mut app = App::new_for_test();
        app.state = AppState::AiRally;
        app.ai_rally_state = Some(make_rally_state());

        assert_snapshot!(render_full(&mut app), @"
        ┌ AI Rally - Iteration 1/3 ────────────────────────────────────────────────────────────────────────┐
        │PR #1                                                                                             │
        │Status: Initializing...                                                                           │
        └──────────────────────────────────────────────────────────────────────────────────────────────────┘
        ┌ History ─────────────────────────────────────────────────────────────────────────────────────────┐
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        └──────────────────────────────────────────────────────────────────────────────────────────────────┘
        ┌ Logs (0/0) [j/k/↑↓: select, Enter: detail] ──────────────────────────────────────────────────────┐
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        └──────────────────────────────────────────────────────────────────────────────────────────────────┘
        ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
        │p: Pause | j/Down/k/Up/↑↓: select | Enter: detail | b: Background | q/Esc: Abort                  │
        └──────────────────────────────────────────────────────────────────────────────────────────────────┘
        ");
    }

    #[test]
    fn test_no_rally_state() {
        let mut app = App::new_for_test();
        app.state = AppState::AiRally;
        app.ai_rally_state = None;

        let output = render_full(&mut app);
        assert!(
            !output.contains("AI Rally"),
            "should render empty when no state"
        );
    }

    #[test]
    fn test_completed_state() {
        let mut app = App::new_for_test();
        app.state = AppState::AiRally;
        let mut rally = make_rally_state();
        rally.state = RallyState::Completed;
        rally.iteration = 3;
        app.ai_rally_state = Some(rally);

        let output = render_full(&mut app);
        assert!(
            output.contains("Completed!"),
            "should show completed status"
        );
    }

    #[test]
    fn test_with_logs() {
        let mut app = App::new_for_test();
        app.state = AppState::AiRally;
        let mut rally = make_rally_state();
        rally.logs.push(LogEntry {
            timestamp: "12:00:00".to_string(),
            event_type: LogEventType::Info,
            message: "Rally started".to_string(),
        });
        rally.logs.push(LogEntry {
            timestamp: "12:00:01".to_string(),
            event_type: LogEventType::Thinking,
            message: "Analyzing code...".to_string(),
        });
        app.ai_rally_state = Some(rally);

        let output = render_full(&mut app);
        assert!(output.contains("Rally started"), "should show log messages");
        assert!(
            output.contains("Analyzing code..."),
            "should show thinking log"
        );
    }

    #[test]
    fn test_paused_state() {
        let mut app = App::new_for_test();
        app.state = AppState::AiRally;
        let mut rally = make_rally_state();
        rally.state = RallyState::ReviewerReviewing;
        rally.pause_state = PauseState::Paused;
        app.ai_rally_state = Some(rally);

        let output = render_full(&mut app);
        assert!(output.contains("PAUSED"), "should show paused indicator");
    }

    #[test]
    fn test_review_only_badge_in_header() {
        let mut app = App::new_for_test();
        app.state = AppState::AiRally;
        app.ai_rally_state = Some(make_rally_state_with(true));

        let output = render_full(&mut app);
        assert!(
            output.contains("[Review Only]"),
            "header should show [Review Only] badge when review_only is set:\n{}",
            output
        );
        assert!(
            output.contains("Iteration 1/3"),
            "review_only is now a proposal-iteration mode and should display Iteration n/max like normal mode:\n{}",
            output
        );
    }

    #[test]
    fn test_review_only_startup_banner_in_history() {
        let mut app = App::new_for_test();
        app.state = AppState::AiRally;
        let mut rally = make_rally_state_with(true);
        rally
            .history
            .push(crate::ai::orchestrator::RallyEvent::RallyStarted { review_only: true });
        app.ai_rally_state = Some(rally);

        let output = render_full(&mut app);
        assert!(
            output.contains("Mode:"),
            "history should show Mode: prefix when RallyStarted with review_only=true:\n{}",
            output
        );
        assert!(
            output.contains("Review Only"),
            "history should show Review Only label:\n{}",
            output
        );
    }

    #[test]
    fn test_normal_mode_rally_started_hidden_from_history() {
        let mut app = App::new_for_test();
        app.state = AppState::AiRally;
        let mut rally = make_rally_state_with(false);
        rally
            .history
            .push(crate::ai::orchestrator::RallyEvent::RallyStarted { review_only: false });
        app.ai_rally_state = Some(rally);

        let output = render_full(&mut app);
        assert!(
            !output.contains("Mode:"),
            "history should not show Mode: entry when review_only is false:\n{}",
            output
        );
    }

    #[test]
    fn test_normal_mode_no_review_only_badge() {
        let mut app = App::new_for_test();
        app.state = AppState::AiRally;
        app.ai_rally_state = Some(make_rally_state_with(false));

        let output = render_full(&mut app);
        assert!(
            !output.contains("[Review Only]"),
            "header should not show [Review Only] badge when review_only is false"
        );
    }
}
