use crate::{app::App, entities::Account, enums::*};
use ratatui::{
    Frame,
    layout::{Constraint, Direction, Layout, Rect},
    style::{Color, Modifier, Style, palette::tailwind::SLATE},
    symbols::Marker,
    text::{Line, Span, Text},
    widgets::{
        Axis, Block, Borders, Chart, Clear, Dataset, GraphType, HighlightSpacing, List, ListItem,
        Paragraph, Row, StatefulWidget, Table, Wrap,
    },
};

const TEXT_FG_COLOR: Color = SLATE.c200;
const SELECTED_STYLE: Style = Style::new().bg(SLATE.c800).add_modifier(Modifier::BOLD);

pub fn ui(frame: &mut Frame, app: &mut App) {
    let layout = Layout::default()
        .direction(Direction::Horizontal)
        .constraints(vec![Constraint::Percentage(20), Constraint::Percentage(80)])
        .split(frame.area());
    let right_layout = Layout::default()
        .direction(Direction::Vertical)
        .constraints(vec![Constraint::Percentage(10), Constraint::Percentage(90)])
        .split(layout[1]);

    let mut ac_block = Block::default()
        .title("Accounts")
        .borders(Borders::ALL)
        .border_style(Style::new().fg(Color::DarkGray));
    let info_block = Block::default()
        .title("Account info")
        .borders(Borders::ALL)
        .border_style(Style::new().fg(Color::DarkGray));
    let mut trx_block = Block::default()
        .title(build_tab_title(app.selected_tab, app.tabs.clone()))
        .borders(Borders::ALL)
        .border_style(Style::new().fg(Color::DarkGray));

    let active_style = Style::default();
    match app.current_widget {
        CurrentWidget::AccountList => ac_block = ac_block.border_style(active_style),
        CurrentWidget::TrxList => trx_block = trx_block.border_style(active_style),
    };

    let ac_items: Vec<ListItem> = app
        .get_list_accounts()
        .iter()
        .enumerate()
        .map(|(_, acc)| ListItem::from(acc))
        .collect();

    let list = List::new(ac_items)
        .block(ac_block)
        .highlight_style(SELECTED_STYLE)
        .highlight_symbol(">")
        .highlight_spacing(HighlightSpacing::Always);

    StatefulWidget::render(list, layout[0], frame.buffer_mut(), &mut app.acc_list.state);

    let info = if let Some(i) = app.acc_list.state.selected() {
        format!(
            "Total: {}",
            app.get_list_accounts()[i]
                .get_total(&app.connection)
                .unwrap_or_default()
                .to_string()
        )
    } else {
        "No account selected...".to_string()
    };
    frame.render_widget(Paragraph::new(info).block(info_block), right_layout[0]);

    if app.selected_tab == 0 {
        let trx_header = Row::new(["Date", "Type", "Amount", "Description"]);
        let trx_rows = app.get_list_trx().into_iter().map(|tr| {
            Row::new([
                tr.get_date().format("%Y-%m-%d").to_string(),
                tr.get_type().get_description(),
                tr.get_amount().to_string(),
                tr.get_desc(),
            ])
            .style(Style::new().fg(if tr.get_amount() > 0.0 {
                Color::Green
            } else {
                Color::Red
            }))
        });
        let widths = [
            Constraint::Percentage(30),
            Constraint::Percentage(20),
            Constraint::Percentage(20),
            Constraint::Percentage(50),
        ];
        let trx_table = Table::new(trx_rows, widths)
            .block(trx_block)
            .header(trx_header)
            .row_highlight_style(SELECTED_STYLE)
            .highlight_symbol(">")
            .highlight_spacing(HighlightSpacing::Always)
            .column_spacing(1);

        StatefulWidget::render(
            trx_table,
            right_layout[1],
            frame.buffer_mut(),
            &mut app.trx_table.state,
        );
    } else if app.selected_tab == 1 {
        let slice: &[(f64, f64)] = &app.get_trtype_data();
        let dataset = Dataset::default()
            .marker(Marker::Braille)
            .graph_type(GraphType::Line)
            .style(Color::Blue)
            .data(slice);

        let min_x = slice
            .into_iter()
            .map(|&(x, _)| x)
            .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Less))
            .unwrap_or_default()
            .round();
        let max_x = slice
            .into_iter()
            .map(|&(x, _)| x)
            .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Greater))
            .unwrap_or_default()
            .round()
            + 1.0;
        let x_axis = Axis::default()
            .title("Temps")
            .bounds([min_x, max_x])
            .labels([min_x.to_string(), max_x.to_string()]);

        let min_y = slice
            .into_iter()
            .map(|&(_, y)| y)
            .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Less))
            .unwrap_or_default()
            .round()
            - 1.0;
        let max_y = slice
            .into_iter()
            .map(|&(_, y)| y)
            .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Greater))
            .unwrap_or_default()
            .round()
            + 1.0;
        let y_axis = Axis::default()
            .title("Coƻts")
            .bounds([min_y, max_y])
            .labels([min_y.to_string(), max_y.to_string()]);

        let chart = Chart::new(vec![dataset])
            .block(trx_block.clone())
            .x_axis(x_axis)
            .y_axis(y_axis);
        frame.render_widget(chart, right_layout[1]);

        let p = Paragraph::new(app.selected_tr_type_chart_desc.clone())
            .block(trx_block)
            .style(Style::default())
            .alignment(ratatui::layout::HorizontalAlignment::Center);
        frame.render_widget(p, right_layout[1]);

        let helper = Block::default()
            .title("Helper")
            .borders(Borders::all())
            .style(Style::default());

        let helper_string: String = app
            .tr_types
            .iter()
            .map(|tr_type| tr_type.get_id().to_string() + " " + &tr_type.get_description() + "\n")
            .collect();
        let helper_text = Text::styled(helper_string, Style::default());
        let helper_paragraph = Paragraph::new(helper_text)
            .block(helper)
            .wrap(Wrap { trim: false });
        let area = helper_rect(frame.area());
        frame.render_widget(Clear, area);
        frame.render_widget(helper_paragraph, area);
    } else if app.selected_tab == 2 {
        let slice: &[(f64, f64)] = &app.get_cumulative_total();
        let dataset = Dataset::default()
            .marker(Marker::Braille)
            .graph_type(GraphType::Line)
            .style(Color::Blue)
            .data(slice);

        let min_x = slice
            .into_iter()
            .map(|&(x, _)| x)
            .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Less))
            .unwrap_or_default()
            .round();
        let max_x = slice
            .into_iter()
            .map(|&(x, _)| x)
            .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Greater))
            .unwrap_or_default()
            .round()
            + 1.0;
        let x_axis = Axis::default()
            .title("Time")
            .bounds([min_x, max_x])
            .labels([min_x.to_string(), max_x.to_string()]);

        let min_y = slice
            .into_iter()
            .map(|&(_, y)| y)
            .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Less))
            .unwrap_or_default()
            .round()
            - 1.0;
        let max_y = slice
            .into_iter()
            .map(|&(_, y)| y)
            .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Greater))
            .unwrap_or_default()
            .round()
            + 1.0;
        let y_axis = Axis::default()
            .title("Total")
            .bounds([min_y, max_y])
            .labels([min_y.to_string(), max_y.to_string()]);

        let chart = Chart::new(vec![dataset])
            .block(trx_block.clone())
            .x_axis(x_axis)
            .y_axis(y_axis);
        frame.render_widget(chart, right_layout[1]);
    } else {
        let p = Paragraph::new("Tab not implemented")
            .block(trx_block)
            .style(Style::default());
        frame.render_widget(p, right_layout[1]);
    }

    if let CurrentScreen::NewAccount = app.current_screen {
        let popup = Block::default()
            .title("New account")
            .borders(Borders::all())
            .style(Style::default());

        let area = centered_rect(50, 40, frame.area());
        frame.render_widget(Clear, area);
        frame.render_widget(popup, area);

        let chunks = Layout::default()
            .direction(Direction::Vertical)
            .margin(1)
            .constraints([
                Constraint::Length(3),
                Constraint::Length(3),
                Constraint::Length(3),
            ])
            .split(area);

        let ac_asset = Paragraph::new(ac_input_str(AccountInput::Asset, app))
            .style(ac_input_style(AccountInput::Asset, app))
            .block(
                Block::default()
                    .borders(Borders::BOTTOM)
                    .title("Asset Price"),
            );
        frame.render_widget(ac_asset, chunks[0]);

        let ac_name = Paragraph::new(ac_input_str(AccountInput::Name, app))
            .style(ac_input_style(AccountInput::Name, app))
            .block(Block::default().borders(Borders::BOTTOM).title("Name"));
        frame.render_widget(ac_name, chunks[1]);

        let ac_type = Paragraph::new(ac_input_str(AccountInput::Type, app))
            .style(ac_input_style(AccountInput::Type, app))
            .block(Block::default().borders(Borders::BOTTOM).title("Type"));
        frame.render_widget(ac_type, chunks[2]);
    }

    if let CurrentScreen::NewTransaction = app.current_screen {
        let popup = Block::default()
            .title("New Transaction")
            .borders(Borders::all())
            .style(Style::default());

        let area = centered_rect(50, 40, frame.area());
        frame.render_widget(Clear, area);
        frame.render_widget(popup, area);

        let chunks = Layout::default()
            .direction(Direction::Vertical)
            .margin(1)
            .constraints([
                Constraint::Length(3),
                Constraint::Length(3),
                Constraint::Length(3),
                Constraint::Length(3),
                Constraint::Length(3),
            ])
            .split(area);

        let tr_type = Paragraph::new(tr_input_str(TransactionInput::Type, app))
            .style(tr_input_style(TransactionInput::Type, app))
            .block(Block::default().borders(Borders::BOTTOM).title("Type"));
        frame.render_widget(tr_type, chunks[0]);

        let amnt = Paragraph::new(tr_input_str(TransactionInput::Amount, app))
            .style(tr_input_style(TransactionInput::Amount, app))
            .block(Block::default().borders(Borders::BOTTOM).title("Amount"));
        frame.render_widget(amnt, chunks[1]);

        let date = Paragraph::new(tr_input_str(TransactionInput::Date, app))
            .style(tr_input_style(TransactionInput::Date, app))
            .block(Block::default().borders(Borders::BOTTOM).title("Date"));
        frame.render_widget(date, chunks[2]);

        let desc = Paragraph::new(tr_input_str(TransactionInput::Description, app))
            .style(tr_input_style(TransactionInput::Description, app))
            .block(
                Block::default()
                    .borders(Borders::BOTTOM)
                    .title("Description"),
            );
        frame.render_widget(desc, chunks[3]);

        let asset_amnt = Paragraph::new(tr_input_str(TransactionInput::Asset, app))
            .style(tr_input_style(TransactionInput::Asset, app))
            .block(
                Block::default()
                    .borders(Borders::BOTTOM)
                    .title("Asset amount"),
            );
        frame.render_widget(asset_amnt, chunks[4]);

        if let TransactionInput::Type = app.selected_transaction_input {
            let helper = Block::default()
                .title("Helper")
                .borders(Borders::all())
                .style(Style::default());

            let helper_string: String = app
                .tr_types
                .iter()
                .map(|tr_type| {
                    tr_type.get_id().to_string() + " " + &tr_type.get_description() + "\n"
                })
                .collect();
            let helper_text = Text::styled(helper_string, Style::default());
            let helper_paragraph = Paragraph::new(helper_text)
                .block(helper)
                .wrap(Wrap { trim: false });
            let area = helper_rect(frame.area());
            frame.render_widget(Clear, area);
            frame.render_widget(helper_paragraph, area);
        }
    }

    if let CurrentScreen::Exiting = app.current_screen {
        let popup = Block::default()
            .title("Exiting program")
            .borders(Borders::all())
            .style(Style::default());

        let exit_text = Text::styled(
            "Are you sure you want to close the program? (y/n)",
            Style::default().fg(Color::Red),
        );

        let exit_paragraph = Paragraph::new(exit_text)
            .block(popup)
            .wrap(Wrap { trim: false });

        let area = centered_rect(50, 20, frame.area());
        frame.render_widget(Clear, area);
        frame.render_widget(exit_paragraph, area);
    }

    if !app.error.is_empty() {
        app.error_showing = true;
        let popup = Block::default()
            .title("Error")
            .borders(Borders::all())
            .style(Style::default());

        let error_text = Text::styled(&app.error, Style::default().fg(Color::Red));

        let error_paragraph = Paragraph::new(error_text)
            .block(popup)
            .wrap(Wrap { trim: false });

        let area = centered_rect(50, 20, frame.area());
        frame.render_widget(Clear, area);
        frame.render_widget(error_paragraph, area);
    }
}

fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
    let popup_layout = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Percentage((100 - percent_y) / 2),
            Constraint::Percentage(percent_y),
            Constraint::Percentage((100 - percent_y) / 2),
        ])
        .split(r);

    return Layout::default()
        .direction(Direction::Horizontal)
        .constraints([
            Constraint::Percentage((100 - percent_x) / 2),
            Constraint::Percentage(percent_x),
            Constraint::Percentage((100 - percent_x) / 2),
        ])
        .split(popup_layout[1])[1];
}
fn helper_rect(r: Rect) -> Rect {
    let popup_layout = Layout::default()
        .direction(Direction::Vertical)
        .constraints([Constraint::Percentage(70), Constraint::Percentage(30)])
        .split(r);

    return Layout::default()
        .direction(Direction::Horizontal)
        .constraints([Constraint::Percentage(20), Constraint::Percentage(70)])
        .split(popup_layout[1])[0];
}

fn tr_input_style(input: TransactionInput, app: &mut App) -> Style {
    if app.selected_transaction_input == input {
        return SELECTED_STYLE;
    }
    return Style::default();
}
fn tr_input_str(input: TransactionInput, app: &mut App) -> String {
    if app.selected_transaction_input == input {
        return app.current_input.clone();
    }
    match input {
        TransactionInput::Type => app.new_transaction.get_type().get_id().to_string(),
        TransactionInput::Amount => app.new_transaction.get_amount().to_string(),
        TransactionInput::Date => app
            .new_transaction
            .get_date()
            .format("%Y-%m-%d")
            .to_string(),
        TransactionInput::Description => app.new_transaction.get_desc(),
        TransactionInput::Asset => app.new_transaction.get_asset_amnt().to_string(),
    }
}
fn ac_input_style(input: AccountInput, app: &mut App) -> Style {
    if app.selected_account_input == input {
        return SELECTED_STYLE;
    }
    return Style::default();
}
fn ac_input_str(input: AccountInput, app: &mut App) -> String {
    if app.selected_account_input == input {
        return app.current_input.clone();
    }
    match input {
        AccountInput::Name => app.new_account.get_name(),
        AccountInput::Type => {
            let ac_type = app.new_account.get_ac_type() as usize;
            return ac_type.to_string();
        }
        AccountInput::Asset => app.new_account.get_asset_price().to_string(),
    }
}
fn build_tab_title(selected: usize, labels: Vec<String>) -> Vec<Span<'static>> {
    let mut spans = Vec::new();

    for (i, label) in labels.iter().enumerate() {
        let style = if i == selected {
            Style::new().fg(Color::Magenta)
        } else {
            Style::new()
        };

        spans.push(Span::raw(label.clone()).style(style));

        if i + 1 < labels.len() {
            spans.push(Span::raw(" | ").style(Style::new().fg(Color::DarkGray)));
        }
    }

    return spans;
}
impl From<&Account> for ListItem<'_> {
    fn from(value: &Account) -> Self {
        let line = Line::styled(
            value.get_id().to_string() + " " + &value.get_name(),
            TEXT_FG_COLOR,
        );
        ListItem::new(line)
    }
}