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

use unicode_width::UnicodeWidthStr;

use super::common::truncate_with_width;
use crate::app::App;
use crate::github::IssueSummary;

pub fn render(frame: &mut Frame, app: &mut App) {
    let Some(ref state) = app.issue_state else {
        return;
    };

    let has_filter_bar = state
        .issue_list_filter
        .as_ref()
        .is_some_and(|f| f.input_active);

    let mut constraints = vec![Constraint::Length(3), Constraint::Min(0)];
    if has_filter_bar {
        constraints.push(Constraint::Length(3));
    }
    constraints.push(Constraint::Length(3));

    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .constraints(constraints)
        .split(frame.area());

    let filter_str = state.issue_list_state_filter.display_name();
    let header_text = format!("Issue List: {} ({})", app.repo, filter_str);
    let header =
        Paragraph::new(header_text).block(Block::default().borders(Borders::ALL).title("octorus"));
    frame.render_widget(header, chunks[0]);

    if matches!(state.issues, crate::app::LoadState::Loading) {
        let loading = Paragraph::new(format!("{} Loading issues...", app.spinner_char()))
            .block(Block::default().borders(Borders::ALL).title("Issues"));
        frame.render_widget(loading, chunks[1]);
    } else if let Some(issues) = state.issues.as_loaded() {
        if issues.is_empty() {
            let empty = Paragraph::new("No issues found")
                .block(Block::default().borders(Borders::ALL).title("Issues"));
            frame.render_widget(empty, chunks[1]);
        } else {
            let (display_issues, display_selected, total_display) =
                if let Some(ref filter) = state.issue_list_filter {
                    if filter.matched_indices.is_empty() {
                        let empty_msg = format!("No matches for '{}'", filter.query);
                        let empty = Paragraph::new(empty_msg)
                            .style(Style::default().fg(Color::DarkGray))
                            .block(
                                Block::default()
                                    .borders(Borders::ALL)
                                    .title(format!("Issues (0/{})", issues.len())),
                            );
                        frame.render_widget(empty, chunks[1]);

                        let mut next_chunk = 2;
                        if has_filter_bar {
                            super::common::render_filter_bar(frame, chunks[next_chunk], filter);
                            next_chunk += 1;
                        }
                        render_footer(frame, chunks[next_chunk], app);
                        return;
                    }
                    let filtered: Vec<&IssueSummary> =
                        filter.matched_indices.iter().map(|&i| &issues[i]).collect();
                    let sel = filter.selected.unwrap_or(0);
                    let total = filtered.len();
                    (filtered, sel, total)
                } else {
                    let all: Vec<&IssueSummary> = issues.iter().collect();
                    let sel = state.selected_issue;
                    let total = all.len();
                    (all, sel, total)
                };

            let total_issues = issues.len();
            let title = if let Some(ref filter) = state.issue_list_filter {
                format!("Issues ({}/{})", filter.matched_indices.len(), total_issues)
            } else if state.issues.is_loading() {
                format!("Issues ({}) {}", total_issues, app.spinner_char())
            } else if state.issue_list_has_more {
                format!("Issues ({}+)", total_issues)
            } else {
                format!("Issues ({})", total_issues)
            };

            let inner_width = chunks[1].width.saturating_sub(3) as usize;
            let items = build_issue_list_items(&display_issues, display_selected, inner_width);

            let mut list_state = ListState::default()
                .with_offset(state.issue_list_scroll_offset)
                .with_selected(Some(display_selected));

            let list = List::new(items)
                .block(Block::default().borders(Borders::ALL).title(title))
                .highlight_style(Style::default().bg(Color::DarkGray));
            frame.render_stateful_widget(list, chunks[1], &mut list_state);

            if let Some(ref mut state) = app.issue_state {
                state.issue_list_scroll_offset = list_state.offset();
            }

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

                let mut scrollbar_state =
                    ScrollbarState::new(total_display.saturating_sub(1)).position(display_selected);

                frame.render_stateful_widget(
                    scrollbar,
                    chunks[1].inner(Margin {
                        vertical: 1,
                        horizontal: 0,
                    }),
                    &mut scrollbar_state,
                );
            }
        }
    } else {
        let empty = Paragraph::new("Failed to load issues")
            .block(Block::default().borders(Borders::ALL).title("Issues"));
        frame.render_widget(empty, chunks[1]);
    }

    let mut next_chunk = 2;
    if has_filter_bar {
        if let Some(ref state) = app.issue_state {
            if let Some(ref filter) = state.issue_list_filter {
                super::common::render_filter_bar(frame, chunks[next_chunk], filter);
            }
        }
        next_chunk += 1;
    }

    render_footer(frame, chunks[next_chunk], app);
}

fn render_footer(frame: &mut Frame, area: ratatui::layout::Rect, app: &App) {
    let help_text = super::footer::footer_hint_back(&app.config.keybindings);
    let line = super::footer::build_footer_line(app, &help_text);
    let footer = Paragraph::new(line).block(Block::default().borders(Borders::ALL));
    frame.render_widget(footer, area);
}

fn build_issue_list_items(
    issues: &[&IssueSummary],
    selected: usize,
    area_width: usize,
) -> Vec<ListItem<'static>> {
    issues
        .iter()
        .enumerate()
        .map(|(i, issue)| {
            let is_selected = i == selected;

            let state_icon = if issue.state.to_lowercase() == "open" {
                Span::styled("● ", Style::default().fg(Color::Green))
            } else {
                Span::styled("● ", Style::default().fg(Color::Magenta))
            };

            let number_style = if is_selected {
                Style::default()
                    .fg(Color::Yellow)
                    .add_modifier(Modifier::BOLD)
            } else {
                Style::default().fg(Color::Yellow)
            };
            let number_span = Span::styled(format!("#{:<5}", issue.number), number_style);

            let author_width = 4 + issue.author.login.width();
            let fixed_width = 2 + 6 + 2 + 2 + author_width;
            let title_width = area_width.saturating_sub(fixed_width).max(20);
            let title = truncate_with_width(&issue.title, title_width);
            let title_style = if is_selected {
                Style::default()
                    .fg(Color::Yellow)
                    .add_modifier(Modifier::BOLD)
            } else {
                Style::default()
            };
            let title_span = Span::styled(
                format!("{:<width$}", title, width = title_width),
                title_style,
            );

            let author_span = Span::styled(
                format!("by @{}", issue.author.login),
                Style::default().fg(Color::Cyan),
            );

            let labels_str = if !issue.labels.is_empty() {
                let label_names: Vec<&str> = issue
                    .labels
                    .iter()
                    .take(2)
                    .map(|l| l.name.as_str())
                    .collect();
                if issue.labels.len() > 2 {
                    format!(" [{}+{}]", label_names.join(", "), issue.labels.len() - 2)
                } else {
                    format!(" [{}]", label_names.join(", "))
                }
            } else {
                String::new()
            };
            let labels_span = Span::styled(labels_str, Style::default().fg(Color::Blue));

            let comment_count = issue.comments.len();
            let comment_span = if comment_count > 0 {
                Span::styled(
                    format!("  💬 {}", comment_count),
                    Style::default().fg(Color::DarkGray),
                )
            } else {
                Span::raw("")
            };

            let line = Line::from(vec![
                state_icon,
                number_span,
                Span::raw("  "),
                title_span,
                Span::raw("  "),
                author_span,
                labels_span,
                comment_span,
            ]);

            ListItem::new(line)
        })
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::app::{App, AppState, IssueState};
    use crate::github::{IssueSummary, Label, User};
    use insta::assert_snapshot;
    use ratatui::backend::TestBackend;
    use ratatui::Terminal;

    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")
    }

    fn make_issue(number: u32, title: &str, state: &str, author: &str) -> IssueSummary {
        IssueSummary {
            number,
            title: title.to_string(),
            state: state.to_string(),
            author: User {
                login: author.to_string(),
            },
            labels: vec![],
            updated_at: "2025-01-01T00:00:00Z".to_string(),
            comments: vec![],
        }
    }

    #[test]
    fn test_empty_issue_list() {
        let mut app = App::new_for_test();
        app.state = AppState::IssueList;
        let mut issue_state = IssueState::new();
        issue_state.issues = crate::app::LoadState::Loaded(vec![]);
        app.issue_state = Some(issue_state);

        assert_snapshot!(render_full(&mut app), @"
        ┌octorus───────────────────────────────────────────────────────────────────────────────────────────┐
        │Issue List: test/repo (open)                                                                      │
        └──────────────────────────────────────────────────────────────────────────────────────────────────┘
        ┌Issues────────────────────────────────────────────────────────────────────────────────────────────┐
        │No issues found                                                                                   │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        └──────────────────────────────────────────────────────────────────────────────────────────────────┘
        ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
        │? Help | ! Shell | q/Esc Back                                                                     │
        └──────────────────────────────────────────────────────────────────────────────────────────────────┘
        ");
    }

    #[test]
    fn test_issue_list_with_items() {
        let mut app = App::new_for_test();
        app.state = AppState::IssueList;
        let mut issue_state = IssueState::new();
        issue_state.issues = crate::app::LoadState::Loaded(vec![
            make_issue(10, "Fix login bug", "open", "alice"),
            make_issue(9, "Add dark mode", "closed", "bob"),
        ]);
        app.issue_state = Some(issue_state);

        assert_snapshot!(render_full(&mut app), @"
        ┌octorus───────────────────────────────────────────────────────────────────────────────────────────┐
        │Issue List: test/repo (open)                                                                      │
        └──────────────────────────────────────────────────────────────────────────────────────────────────┘
        ┌Issues (2)────────────────────────────────────────────────────────────────────────────────────────┐
        │● #10     Fix login bug                                                                 by @alice ▲
        │● #9      Add dark mode                                                                   by @bob █
        │                                                                                                  █
        │                                                                                                  █
        │                                                                                                  █
        │                                                                                                  █
        │                                                                                                  █
        │                                                                                                  █
        │                                                                                                  █
        │                                                                                                  █
        │                                                                                                  █
        │                                                                                                  █
        │                                                                                                  █
        │                                                                                                  █
        │                                                                                                  █
        │                                                                                                  ▼
        └──────────────────────────────────────────────────────────────────────────────────────────────────┘
        ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
        │? Help | ! Shell | q/Esc Back                                                                     │
        └──────────────────────────────────────────────────────────────────────────────────────────────────┘
        ");
    }

    #[test]
    fn test_issue_list_with_labels() {
        let mut app = App::new_for_test();
        app.state = AppState::IssueList;
        let mut issue_state = IssueState::new();
        let mut issue = make_issue(5, "Bug report", "open", "carol");
        issue.labels = vec![
            Label {
                name: "bug".to_string(),
            },
            Label {
                name: "priority".to_string(),
            },
        ];
        issue_state.issues = crate::app::LoadState::Loaded(vec![issue]);
        app.issue_state = Some(issue_state);

        assert_snapshot!(render_full(&mut app), @"
        ┌octorus───────────────────────────────────────────────────────────────────────────────────────────┐
        │Issue List: test/repo (open)                                                                      │
        └──────────────────────────────────────────────────────────────────────────────────────────────────┘
        ┌Issues (1)────────────────────────────────────────────────────────────────────────────────────────┐
        │● #5      Bug report                                                                    by @carol │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        │                                                                                                  │
        └──────────────────────────────────────────────────────────────────────────────────────────────────┘
        ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
        │? Help | ! Shell | q/Esc Back                                                                     │
        └──────────────────────────────────────────────────────────────────────────────────────────────────┘
        ");
    }

    #[test]
    fn test_issue_list_no_state() {
        let mut app = App::new_for_test();
        app.state = AppState::IssueList;
        app.issue_state = None;

        let output = render_full(&mut app);
        assert!(
            !output.contains("Issues"),
            "no issue panel when state is None"
        );
    }
}
