use crossterm::event::{KeyCode, KeyEvent};
use ratatui::{
    layout::{Constraint, Direction, Layout, Rect},
    style::{Color, Modifier, Style},
    text::{Line, Span},
    widgets::{Block, Borders, Cell, List, ListItem, ListState, Paragraph, Row, Table, TableState},
    Frame,
};
use sqlx::PgPool;

use crate::tui::app::{copy_to_clipboard, AppAction};

struct TableSummary {
    name: String,
    row_count: i64,
}

pub struct SchemaVisualizerTab {
    tables: Vec<TableSummary>,
    table_list_state: ListState,
    filter: String,
    filter_active: bool,
    columns: Vec<rok_orm::ColumnInfo>,
    foreign_keys: Vec<rok_orm::ForeignKeyInfo>,
    indexes: Vec<rok_orm::IndexInfo>,
    col_table_state: TableState,
    status: String,
}

impl Default for SchemaVisualizerTab {
    fn default() -> Self {
        let mut table_list_state = ListState::default();
        table_list_state.select(Some(0));
        Self {
            tables: Vec::new(),
            table_list_state,
            filter: String::new(),
            filter_active: false,
            columns: Vec::new(),
            foreign_keys: Vec::new(),
            indexes: Vec::new(),
            col_table_state: TableState::default(),
            status: "Loading...".into(),
        }
    }
}

impl SchemaVisualizerTab {
    pub async fn load(&mut self, pool: &PgPool) {
        let tables = fetch_table_list(pool).await;
        self.tables = match tables {
            Ok(t) => {
                self.status = format!("{} tables", t.len());
                t
            }
            Err(e) => {
                self.status = format!("Error: {e}");
                Vec::new()
            }
        };
        let selected = self.table_list_state.selected().unwrap_or(0);
        if !self.tables.is_empty() && selected >= self.tables.len() {
            self.table_list_state.select(Some(self.tables.len().saturating_sub(1)));
        }
        self.load_selected_table(pool).await;
    }

    async fn load_selected_table(&mut self, pool: &PgPool) {
        let Some(idx) = self.table_list_state.selected() else { return };
        let tables = self.filtered_tables();
        let Some(table) = tables.get(idx) else { return };

        let name = &table.name;
        let cols = rok_orm::SchemaInspector::columns(name, pool).await;
        let fks = rok_orm::SchemaInspector::foreign_keys(name, pool).await;
        let idxs = rok_orm::SchemaInspector::indexes(name, pool).await;

        self.columns = cols.unwrap_or_default();
        self.foreign_keys = fks.unwrap_or_default();
        self.indexes = idxs.unwrap_or_default();
    }

    fn filtered_tables(&self) -> Vec<&TableSummary> {
        if self.filter.is_empty() {
            self.tables.iter().collect()
        } else {
            let f = self.filter.to_lowercase();
            self.tables
                .iter()
                .filter(|t| t.name.to_lowercase().contains(&f))
                .collect()
        }
    }

    pub fn render(&mut self, frame: &mut Frame, area: Rect) {
        let chunks = Layout::default()
            .direction(Direction::Horizontal)
            .constraints([Constraint::Ratio(1, 3), Constraint::Ratio(2, 3)])
            .split(area);

        self.render_table_list(frame, chunks[0]);
        self.render_table_detail(frame, chunks[1]);
    }

    fn render_table_list(&mut self, frame: &mut Frame, area: Rect) {
        let filtered = self.filtered_tables();
        let max_rows = filtered.iter().map(|t| t.row_count).max().unwrap_or(1).max(1);

        let items: Vec<ListItem> = filtered
            .iter()
            .map(|t| {
                let bar_len = ((t.row_count as f64 / max_rows as f64) * 10.0) as usize;
                let bar = "▉".repeat(bar_len);
                let count_str = if t.row_count > 1_000_000 {
                    format!("{:.1}M", t.row_count as f64 / 1_000_000.0)
                } else if t.row_count > 1_000 {
                    format!("{:.1}K", t.row_count as f64 / 1_000.0)
                } else {
                    t.row_count.to_string()
                };
                let label = format!(" {} {:>8} {}", bar, count_str, t.name);
                ListItem::new(Line::from(Span::raw(label)))
            })
            .collect();

        let title = if self.filter_active {
            format!(" Tables — filter: {} ", self.filter)
        } else {
            format!(" Tables — {} ", self.status)
        };

        let list = List::new(items)
            .block(
                Block::default()
                    .title(title)
                    .borders(Borders::ALL)
                    .border_style(Style::default().fg(Color::Cyan)),
            )
            .highlight_style(Style::default().bg(Color::DarkGray))
            .highlight_symbol("▶ ");

        frame.render_stateful_widget(list, area, &mut self.table_list_state);
    }

    fn render_table_detail(&mut self, frame: &mut Frame, area: Rect) {
        let tables = self.filtered_tables();
        let selected = self
            .table_list_state
            .selected()
            .and_then(|i| tables.get(i).copied());

        let Some(table) = selected else {
            let para = Paragraph::new(" Select a table from the left pane ")
                .style(Style::default().fg(Color::DarkGray))
                .block(Block::default().title(" Detail ").borders(Borders::ALL));
            frame.render_widget(para, area);
            return;
        };

        let header = format!(" Table: {} ", table.name);
        let chunks = Layout::default()
            .direction(Direction::Vertical)
            .constraints([
                Constraint::Min(5),
                Constraint::Length(5.min(area.height / 4)),
                Constraint::Length(4.min(area.height / 4)),
            ])
            .split(area);

        self.render_columns(frame, chunks[0], &header);
        self.render_relationships(frame, chunks[1]);
        self.render_indexes(frame, chunks[2]);
    }

    fn render_columns(&mut self, frame: &mut Frame, area: Rect, header: &str) {
        let col_header = Row::new(vec![
            Cell::from("Column").style(Style::default().add_modifier(Modifier::BOLD)),
            Cell::from("Type").style(Style::default().add_modifier(Modifier::BOLD)),
            Cell::from("Nullable").style(Style::default().add_modifier(Modifier::BOLD)),
            Cell::from("Default").style(Style::default().add_modifier(Modifier::BOLD)),
            Cell::from("Key").style(Style::default().add_modifier(Modifier::BOLD)),
        ])
        .height(1);

        let rows: Vec<Row> = self
            .columns
            .iter()
            .map(|c| {
                let nullable_str = if c.nullable { "YES" } else { "NO" };
                let key_str = if c.is_pk { "PK" } else { "" };
                Row::new(vec![
                    Cell::from(c.name.as_str()).style(Style::default().fg(Color::White)),
                    Cell::from(c.data_type.as_str()).style(Style::default().fg(Color::Yellow)),
                    Cell::from(nullable_str).style(Style::default().fg(if c.nullable {
                        Color::Green
                    } else {
                        Color::Red
                    })),
                    Cell::from(
                        c.default
                            .as_deref()
                            .unwrap_or("")
                    )
                    .style(Style::default().fg(Color::DarkGray)),
                    Cell::from(key_str)
                        .style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),
                ])
            })
            .collect();

        let widths = [
            Constraint::Length(18),
            Constraint::Length(24),
            Constraint::Length(10),
            Constraint::Length(28),
            Constraint::Length(5),
        ];

        let total = self.columns.len();
        let table = Table::new(rows, widths)
            .header(col_header)
            .block(
                Block::default()
                    .title(format!("{header} Columns ({total}) "))
                    .borders(Borders::ALL),
            )
            .highlight_style(Style::default().bg(Color::DarkGray))
            .highlight_symbol("▸ ");

        frame.render_stateful_widget(table, area, &mut self.col_table_state);
    }

    fn render_relationships(&mut self, frame: &mut Frame, area: Rect) {
        if self.foreign_keys.is_empty() {
            let para = Paragraph::new(" No foreign key relationships ")
                .style(Style::default().fg(Color::DarkGray))
                .block(
                    Block::default()
                        .title(" Relationships ")
                        .borders(Borders::ALL),
                );
            frame.render_widget(para, area);
            return;
        }

        let rows: Vec<Row> = self
            .foreign_keys
            .iter()
            .map(|fk| {
                let local_cols = fk.columns.join(", ");
                let fk_cols = fk.foreign_columns.join(", ");
                Row::new(vec![
                    Cell::from(local_cols).style(Style::default().fg(Color::Cyan)),
                    Cell::from("→").style(Style::default().fg(Color::DarkGray)),
                    Cell::from(fk.foreign_table.as_str())
                        .style(Style::default().fg(Color::Green)),
                    Cell::from(format!("({fk_cols})"))
                        .style(Style::default().fg(Color::DarkGray)),
                ])
            })
            .collect();

        let widths = [
            Constraint::Length(18),
            Constraint::Length(3),
            Constraint::Length(18),
            Constraint::Min(10),
        ];

        let table = Table::new(rows, widths).block(
            Block::default()
                .title(format!(" Relationships ({}) ", self.foreign_keys.len()))
                .borders(Borders::ALL),
        );

        frame.render_widget(table, area);
    }

    fn render_indexes(&mut self, frame: &mut Frame, area: Rect) {
        if self.indexes.is_empty() {
            let para = Paragraph::new(" No indexes ")
                .style(Style::default().fg(Color::DarkGray))
                .block(Block::default().title(" Indexes ").borders(Borders::ALL));
            frame.render_widget(para, area);
            return;
        }

        let rows: Vec<Row> = self
            .indexes
            .iter()
            .map(|idx| {
                let kind = if idx.primary {
                    "PK"
                } else if idx.unique {
                    "UNIQUE"
                } else {
                    "BTREE"
                };
                let cols = idx.columns.join(", ");
                Row::new(vec![
                    Cell::from(idx.name.as_str()).style(Style::default().fg(Color::Magenta)),
                    Cell::from(kind).style(Style::default().fg(if idx.primary {
                        Color::Cyan
                    } else if idx.unique {
                        Color::Yellow
                    } else {
                        Color::DarkGray
                    })),
                    Cell::from(cols).style(Style::default().fg(Color::White)),
                ])
            })
            .collect();

        let widths = [
            Constraint::Length(24),
            Constraint::Length(8),
            Constraint::Min(10),
        ];

        let table = Table::new(rows, widths).block(
            Block::default()
                .title(format!(" Indexes ({}) ", self.indexes.len()))
                .borders(Borders::ALL),
        );

        frame.render_widget(table, area);
    }

    pub fn handle_key(&mut self, key: KeyEvent) -> AppAction {
        if self.filter_active {
            match key.code {
                KeyCode::Esc => {
                    self.filter_active = false;
                    self.filter.clear();
                    AppAction::Reload
                }
                KeyCode::Char(c) => {
                    self.filter.push(c);
                    self.table_list_state.select(Some(0));
                    AppAction::None
                }
                KeyCode::Backspace => {
                    self.filter.pop();
                    self.table_list_state.select(Some(0));
                    AppAction::None
                }
                KeyCode::Enter => {
                    self.filter_active = false;
                    AppAction::Reload
                }
                _ => AppAction::None,
            }
        } else {
            match key.code {
                KeyCode::Up => {
                    let tables = self.filtered_tables();
                    let max = tables.len().saturating_sub(1);
                    let i = self.table_list_state.selected().unwrap_or(0).saturating_sub(1).min(max);
                    self.table_list_state.select(Some(i));
                    AppAction::None
                }
                KeyCode::Down => {
                    let tables = self.filtered_tables();
                    let max = tables.len().saturating_sub(1);
                    let i = (self.table_list_state.selected().unwrap_or(0) + 1).min(max);
                    self.table_list_state.select(Some(i));
                    AppAction::None
                }
                KeyCode::Char('/') => {
                    self.filter_active = true;
                    self.filter.clear();
                    AppAction::None
                }
                KeyCode::Char('c') | KeyCode::Char('C') => {
                    if let Some(idx) = self.table_list_state.selected() {
                        let tables = self.filtered_tables();
                        if let Some(t) = tables.get(idx) {
                            copy_to_clipboard(&t.name);
                        }
                    }
                    AppAction::None
                }
                KeyCode::F(5) | KeyCode::Char('r') | KeyCode::Char('R') => AppAction::Reload,
                _ => AppAction::None,
            }
        }
    }
}

async fn fetch_table_list(pool: &PgPool) -> anyhow::Result<Vec<TableSummary>> {
    use sqlx::Row as _;

    let rows = sqlx::query(
        "SELECT t.table_name, \
                (SELECT reltuples::BIGINT FROM pg_class WHERE oid = (quote_ident(t.table_schema) || '.' || quote_ident(t.table_name))::regclass) AS row_estimate \
         FROM information_schema.tables t \
         WHERE t.table_schema = 'public' AND t.table_type = 'BASE TABLE' \
         ORDER BY t.table_name",
    )
    .fetch_all(pool)
    .await?;

    Ok(rows
        .into_iter()
        .map(|r| TableSummary {
            name: r.try_get::<String, _>("table_name").unwrap_or_default(),
            row_count: r.try_get::<i64, _>("row_estimate").unwrap_or(0),
        })
        .collect())
}
