use std::default::Default;
use std::fmt;

use color_eyre::eyre::Result;
use crossterm::event::{KeyCode, KeyEvent};
use log::{debug, info};
use ratatui::layout::Constraint::Percentage;
use ratatui::widgets::block::{Position, Title};
use ratatui::widgets::TableState;
use ratatui::{prelude::*, widgets::*};
use tokio::sync::mpsc::UnboundedSender;
use tui_input::Input;

use super::{Component, Frame};
use crate::action::Action;
use crate::components::process::Order::{Command, Cpu, Name, NumberOfThreads, Pid};
use crate::model::{create_rows, get_all_processes, get_processes, BrtProcess};

#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)]
pub enum Order {
    #[default]
    Pid,
    Name,
    Command,
    NumberOfThreads,
    Cpu,
}

impl Order {
    fn next(&self) -> Self {
        use Order::*;
        match *self {
            Pid => Name,
            Name => Command,
            Command => NumberOfThreads,
            NumberOfThreads => Cpu,
            Cpu => Pid,
        }
    }

    fn previous(&self) -> Self {
        use Order::*;
        match *self {
            Pid => Cpu,
            Cpu => NumberOfThreads,
            NumberOfThreads => Command,
            Command => Name,
            Name => Pid,
        }
    }
}

impl fmt::Display for Order {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Pid => write!(f, "pid"),
            Name => write!(f, "name"),
            Command => write!(f, "command"),
            NumberOfThreads => write!(f, "threads"),
            Cpu => write!(f, "cpu"),
        }
    }
}

pub struct Process {
    pub show_help: bool,
    pub app_ticker: usize,
    pub render_ticker: usize,
    pub input: Input,
    pub processes: Vec<BrtProcess>,
    pub order: Order,
    pub scrollbar_state: ScrollbarState,
    pub state: TableState,
    pub action_tx: Option<UnboundedSender<Action>>,
}

impl Default for Process {
    fn default() -> Process {
        let processes = Self::get_processes();
        let length = processes.len();
        Process {
            show_help: false,
            app_ticker: 0,
            render_ticker: 0,
            input: Default::default(),
            processes,
            order: Default::default(),
            scrollbar_state: ScrollbarState::new(length),
            state: TableState::new().with_selected(Some(0)),
            action_tx: None,
        }
    }
}

impl Process {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn order_string(&mut self) -> String {
        format!("{} {} {}", "<".red(), self.order, ">".red())
    }

    pub fn tick(&mut self) {
        self.app_ticker = self.app_ticker.saturating_add(1);
        if self.app_ticker % 5 == 0 {
            self.processes = Self::get_processes();
            self.order_by_enum();
            info!("Refreshed process list.");
        }
    }

    pub fn order_by_enum(&mut self) {
        let order = self.order;
        match order {
            Pid => self.order_by_pid(),
            Name => self.order_by_program(),
            Command => self.order_by_command(),
            NumberOfThreads => self.order_by_number_of_threads(),
            Cpu => self.order_by_cpu(),
        }
    }

    pub fn order_by_pid(&mut self) {
        self.processes.sort_by(|a, b| a.pid.cmp(&b.pid))
    }

    pub fn order_by_program(&mut self) {
        self.processes.sort_by(|a, b| a.program.cmp(&b.program))
    }

    pub fn order_by_command(&mut self) {
        self.processes.sort_by(|a, b| a.command.cmp(&b.command))
    }

    pub fn order_by_number_of_threads(&mut self) {
        self.processes.sort_by(|a, b| {
            a.number_of_threads
                .partial_cmp(&b.number_of_threads)
                .unwrap()
        })
    }

    pub fn order_by_cpu(&mut self) {
        self.processes
            .sort_by(|a, b| a.cpu.partial_cmp(&b.cpu).unwrap())
    }

    pub fn get_processes() -> Vec<BrtProcess> {
        let processes = get_all_processes();
        let processes = get_processes(&processes);
        info!("Found {} processes.", processes.len());
        processes
    }

    pub fn render_tick(&mut self) {
        debug!("Render Tick");
        self.render_ticker = self.render_ticker.saturating_add(1);
    }

    pub fn jump(&mut self, steps: i64) {
        let location = self.state.selected().unwrap_or(0) as i64;
        let length = self.processes.len() as i64;
        debug!(
            "Move {} steps in [{}..{}] when current location is {}.",
            steps, 0, length, location
        );
        let mut index = location + steps;
        while index < 0 {
            index += length;
        }
        let new_location = (index % length) as usize;
        debug!("New location is {}.", new_location);
        self.state.select(Some(new_location));
        self.scrollbar_state = self.scrollbar_state.position(new_location);
    }
}

impl Component for Process {
    fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
        self.action_tx = Some(tx);
        Ok(())
    }

    fn handle_key_events(&mut self, key: KeyEvent) -> Result<Option<Action>> {
        debug!("Handling {:?}.", key);
        let action = match key.code {
            KeyCode::Up => Action::Up,
            KeyCode::Down => Action::Down,
            KeyCode::PageUp => Action::PageUp,
            KeyCode::PageDown => Action::PageDown,
            KeyCode::Left => Action::Left,
            KeyCode::Right => Action::Right,
            KeyCode::Esc => Action::Quit,
            _ => Action::Update,
        };
        Ok(Some(action))
    }

    fn update(&mut self, action: Action) -> Result<Option<Action>> {
        match action {
            Action::Tick => self.tick(),
            Action::Render => self.render_tick(),
            Action::Up => self.jump(-1),
            Action::Down => self.jump(1),
            Action::PageUp => self.jump(-20),
            Action::PageDown => self.jump(20),
            Action::Left => {
                self.order = self.order.previous();
                self.order_by_enum();
            }
            Action::Right => {
                self.order = self.order.next();
                self.order_by_enum();
            }
            _ => (),
        }
        Ok(None)
    }

    fn draw(&mut self, f: &mut Frame<'_>, _rect: Rect) -> Result<()> {
        let layout = Layout::default()
            .direction(Direction::Vertical)
            .constraints([Percentage(100)])
            .split(f.size());

        let rows = create_rows(&self.processes);

        let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
            .begin_symbol(Some("↑"))
            .end_symbol(Some("↓"))
            .track_symbol(Some(" "))
            .style(Color::White);

        let selected_style = Style::default()
            .bg(Color::Rgb(0xd4, 0x54, 0x54))
            .fg(Color::White)
            .add_modifier(Modifier::BOLD);

        let header = [
            Cell::new(Line::from("Pid:").alignment(Alignment::Right)),
            Cell::new("Program:"),
            Cell::new("Command:"),
            Cell::new(Line::from("Threads:").alignment(Alignment::Right)),
            Cell::new("User:"),
            Cell::new("MemB"),
            Cell::new("Cpu%"),
        ]
        .iter()
        .cloned()
        .map(Cell::from)
        .collect::<Row>()
        .height(1)
        .style(Style::default().bold());

        let processes = self.processes.len();
        let process = format!("{}/{}", self.state.selected().unwrap() + 1, processes);

        let block = Block::default()
            .title(Title::from("brt").alignment(Alignment::Center))
            .title(Title::from(self.order_string()).alignment(Alignment::Right))
            .title(
                Title::from(process)
                    .position(Position::Bottom)
                    .alignment(Alignment::Right),
            )
            .borders(Borders::ALL)
            .border_style(Style::default().fg(Color::White))
            .border_type(BorderType::Rounded);

        let widths = [
            Percentage(5),
            Percentage(15),
            Percentage(60),
            Percentage(5),
            Percentage(5),
            Percentage(5),
            Percentage(5),
        ];

        let table = Table::new(rows, widths)
            .block(block)
            .header(header)
            .highlight_style(selected_style);

        f.render_stateful_widget(table, layout[0], &mut self.state);
        f.render_stateful_widget(
            scrollbar,
            layout[0].inner(&Margin {
                vertical: 1,
                horizontal: 1,
            }),
            &mut self.scrollbar_state,
        );
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_process_jump() {
        let mut process = Process::default();
        assert_eq!(process.state.selected(), Some(0));
        process.jump(5);
        assert_eq!(process.state.selected(), Some(5));
        process.jump(5);
        assert_eq!(process.state.selected(), Some(10));
        process.jump(-15);
        assert_eq!(process.state.selected(), Some(process.processes.len() - 5));
        process.jump(4);
        assert_eq!(process.state.selected(), Some(process.processes.len() - 1));
        process.jump(1);
        assert_eq!(process.state.selected(), Some(0));
        process.jump(1);
        assert_eq!(process.state.selected(), Some(1));
    }
}
