use bollard::models::ContainerSummary;
use core::fmt;
use parking_lot::Mutex;
use ratatui::widgets::{ListItem, ListState};
use std::{
    hash::Hash,
    sync::Arc,
    time::{SystemTime, UNIX_EPOCH},
};

mod container_state;

use crate::{
    app_error::AppError,
    parse_args::CliArgs,
    ui::{log_sanitizer, GuiState, Status},
    ENTRY_POINT,
};
pub use container_state::*;

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum SortedOrder {
    Asc,
    Desc,
}

#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
pub enum Header {
    State,
    Status,
    Cpu,
    Memory,
    Id,
    Name,
    Image,
    Rx,
    Tx,
}

/// Convert Header enum into strings to display
impl fmt::Display for Header {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let disp = match self {
            Self::State => "state",
            Self::Status => "status",
            Self::Cpu => "cpu",
            Self::Memory => "memory/limit",
            Self::Id => "id",
            Self::Name => "name",
            Self::Image => "image",
            Self::Rx => "↓ rx",
            Self::Tx => "↑ tx",
        };
        write!(f, "{disp:>x$}", x = f.width().unwrap_or(1))
    }
}

#[derive(Debug, Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum FilterBy {
    #[default]
    Name,
    Image,
    Status,
    All,
}

/// Convert errors into strings to display
impl fmt::Display for FilterBy {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Self::Name => "Name",
                Self::Image => "Image",
                Self::Status => "Status",
                Self::All => "All",
            }
        )
    }
}

impl FilterBy {
    const fn next(self) -> Option<Self> {
        match self {
            Self::Name => Some(Self::Image),
            Self::Image => Some(Self::Status),
            Self::Status => Some(Self::All),
            Self::All => None,
        }
    }

    const fn prev(self) -> Option<Self> {
        match self {
            Self::Name => None,
            Self::Image => Some(Self::Name),
            Self::Status => Some(Self::Image),
            Self::All => Some(Self::Status),
        }
    }
}

#[derive(Debug, Clone)]
pub struct Filter {
    pub term: Option<String>,
    pub by: FilterBy,
}
impl Filter {
    pub fn new() -> Self {
        Self {
            term: None,
            by: FilterBy::default(),
        }
    }
}

/// Global app_state, stored in an Arc<Mutex>
#[derive(Debug, Clone)]
#[cfg(not(test))]
pub struct AppData {
    containers: StatefulList<ContainerItem>,
    error: Option<AppError>,
    filter: Filter,
    hidden_containers: Vec<ContainerItem>,
    sorted_by: Option<(Header, SortedOrder)>,
    pub args: CliArgs,
}

#[derive(Debug, Clone)]
#[cfg(test)]
pub struct AppData {
    pub args: CliArgs,
    pub containers: StatefulList<ContainerItem>,
    pub error: Option<AppError>,
    pub filter: Filter,
    pub hidden_containers: Vec<ContainerItem>,
    pub sorted_by: Option<(Header, SortedOrder)>,
}

impl AppData {
    /// Generate a default app_state
    pub fn default(args: CliArgs) -> Self {
        Self {
            args,
            containers: StatefulList::new(vec![]),
            hidden_containers: vec![],
            error: None,
            sorted_by: None,
            filter: Filter::new(),
        }
    }

    /// Current time as unix timestamp
    #[allow(clippy::expect_used)]
    fn get_systemtime() -> u64 {
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("In our known reality, this error should never occur")
            .as_secs()
    }

    /// Filter related methods

    /// Get the current filter term
    pub const fn get_filter_term(&self) -> Option<&String> {
        self.filter.term.as_ref()
    }

    /// Get the current filter by choice
    pub const fn get_filter_by(&self) -> FilterBy {
        self.filter.by
    }

    /// Check if a given container can be inserted into the "visible" list, based on current filter term and filter_by
    fn can_insert(&self, container: &ContainerItem) -> bool {
        self.filter.term.as_ref().map_or(true, |term| {
            let term = term.to_lowercase();
            match self.filter.by {
                FilterBy::All => {
                    container.name.contains(&term)
                        || container.image.contains(&term)
                        || container.status.contains(&term)
                }
                FilterBy::Image => container.image.contains(&term),
                FilterBy::Name => container.name.contains(&term),
                FilterBy::Status => container.status.contains(&term),
            }
        })
    }

    /// Remove items from the containers list based on the filter term, and insert into a "hidden" vec
    /// sets the state to start if any filtering has occurred
    /// Also search in the "hidden" vec for items and insert back into the main containers vec
    fn filter_containers(&mut self) {
        let pre_len = self.get_container_len();

        if !self.hidden_containers.is_empty() {
            let (mut new_items, tmp_items): (Vec<_>, Vec<_>) = self
                .hidden_containers
                .iter()
                .cloned()
                .partition(|item| self.can_insert(item));

            while let Some(x) = new_items.pop() {
                self.containers.items.push(x);
            }
            self.hidden_containers = tmp_items;
        }

        let (new_items, tmp_items) = self
            .containers
            .items
            .iter()
            .cloned()
            .partition(|item| self.can_insert(item));

        self.containers.items = new_items;
        self.hidden_containers.extend(tmp_items);

        self.sort_containers();
        if self.get_container_len() != pre_len {
            self.containers.start();
        }
    }

    /// Re-filter the containers, used after the filter.by has been changed
    fn re_filter(&mut self) {
        self.containers.items.append(&mut self.hidden_containers);
        self.hidden_containers = vec![];
        self.filter_containers();
    }

    /// Set a single char into the filter term
    pub fn filter_term_push(&mut self, c: char) {
        if let Some(term) = self.filter.term.as_mut() {
            term.push(c);
        } else {
            self.filter.term = Some(format!("{c}"));
        };
        self.filter_containers();
    }

    /// Delete the final char of the filter term
    pub fn filter_term_pop(&mut self) {
        if let Some(term) = self.filter.term.as_mut() {
            // should now search for items in the tmp vec, and insert into containers if found
            term.pop();
            if term.is_empty() {
                self.filter.term = None;
            }
        }
        self.filter_containers();
    }

    // change the filter_by option
    pub fn filter_by_next(&mut self) {
        if let Some(by) = self.filter.by.next() {
            self.filter.by = by;
            self.re_filter();
        }
    }

    // change the filter_by option
    pub fn filter_by_prev(&mut self) {
        if let Some(by) = self.filter.by.prev() {
            self.filter.by = by;
            self.re_filter();
        }
    }

    /// Remove the filter completely
    pub fn filter_term_clear(&mut self) {
        self.filter.term = None;
        while let Some(i) = self.hidden_containers.pop() {
            if self.get_container_by_id(&i.id).is_none() {
                self.containers.items.push(i);
            };
        }
        self.sort_containers();
    }

    /// Container sort related methods

    /// Change the sorted order, also set the selected container state to match new order
    fn set_sorted(&mut self, x: Option<(Header, SortedOrder)>) {
        self.sorted_by = x;
        self.sort_containers();
        self.containers
            .state
            .select(self.containers.items.iter().position(|i| {
                self.get_selected_container_id()
                    .map_or(false, |id| i.id == id)
            }));
    }

    /// Remove the sorted header & order, and sort by default - created datetime
    pub fn reset_sorted(&mut self) {
        self.set_sorted(None);
    }

    /// Sort containers based on a given header, if headings match, and already ascending, remove sorting
    pub fn set_sort_by_header(&mut self, selected_header: Header) {
        let mut output = Some((selected_header, SortedOrder::Asc));
        if let Some((current_header, order)) = self.get_sorted() {
            if current_header == selected_header {
                match order {
                    SortedOrder::Desc => output = None,
                    SortedOrder::Asc => output = Some((selected_header, SortedOrder::Desc)),
                }
            }
        }
        self.set_sorted(output);
    }

    pub const fn get_sorted(&self) -> Option<(Header, SortedOrder)> {
        self.sorted_by
    }

    /// Sort the containers vec, based on a heading (and if clash, then by name), either ascending or descending,
    /// If not sort set, then sort by created time
    pub fn sort_containers(&mut self) {
        if let Some((head, ord)) = self.sorted_by {
            let sort_closure = |a: &ContainerItem, b: &ContainerItem| -> std::cmp::Ordering {
                let item_ord = match ord {
                    SortedOrder::Asc => (a, b),
                    SortedOrder::Desc => (b, a),
                };
                match head {
                    Header::State => item_ord
                        .0
                        .state
                        .order()
                        .cmp(&item_ord.1.state.order())
                        .then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),
                    Header::Status => item_ord
                        .0
                        .status
                        .get()
                        .cmp(item_ord.1.status.get())
                        .then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),
                    Header::Cpu => item_ord
                        .0
                        .cpu_stats
                        .back()
                        .cmp(&item_ord.1.cpu_stats.back())
                        .then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),
                    Header::Memory => item_ord
                        .0
                        .mem_stats
                        .back()
                        .cmp(&item_ord.1.mem_stats.back())
                        .then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),

                    Header::Id => item_ord
                        .0
                        .id
                        .cmp(&item_ord.1.id)
                        .then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),
                    Header::Image => item_ord
                        .0
                        .image
                        .get()
                        .cmp(item_ord.1.image.get())
                        .then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),
                    Header::Rx => item_ord
                        .0
                        .rx
                        .cmp(&item_ord.1.rx)
                        .then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),
                    Header::Tx => item_ord
                        .0
                        .tx
                        .cmp(&item_ord.1.tx)
                        .then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),

                    Header::Name => item_ord
                        .0
                        .name
                        .get()
                        .cmp(item_ord.1.name.get())
                        .then_with(|| item_ord.0.id.cmp(&item_ord.1.id)),
                }
            };
            self.containers.items.sort_by(sort_closure);
        } else {
            self.containers.items.sort_by(|a, b| {
                a.created
                    .cmp(&b.created)
                    .then_with(|| a.name.get().cmp(b.name.get()))
            });
        }
    }

    /// Container state methods

    /// Get the total number of none "hidden" containers
    pub fn get_container_len(&self) -> usize {
        self.containers.items.len()
    }

    /// Get all the ContainerItems
    pub fn get_container_items(&self) -> &[ContainerItem] {
        &self.containers.items
    }

    /// Get title for containers section, add a suffix indicating if the containers are currently under filter
    pub fn container_title(&self) -> String {
        let suffix = if !self.hidden_containers.is_empty() && !self.containers.items.is_empty() {
            " - filtered"
        } else {
            ""
        };
        format!("{}{}", self.containers.get_state_title(), suffix)
    }

    /// Select the first container
    pub fn containers_start(&mut self) {
        self.containers.start();
    }

    /// select the last container
    pub fn containers_end(&mut self) {
        self.containers.end();
    }

    /// Select the next container
    pub fn containers_next(&mut self) {
        self.containers.next();
    }

    /// select the previous container
    pub fn containers_previous(&mut self) {
        self.containers.previous();
    }

    /// Get ListState of containers
    pub fn get_container_state(&mut self) -> &mut ListState {
        &mut self.containers.state
    }

    /// Get Option of the current selected container
    pub fn get_selected_container(&self) -> Option<&ContainerItem> {
        self.containers
            .state
            .selected()
            .and_then(|i| self.containers.items.get(i))
    }

    /// Find the longest port when it's transformed into a string, defaults are header lens (ip, private, public)
    pub fn get_longest_port(&self) -> (usize, usize, usize) {
        let mut longest_ip = 5;
        let mut longest_private = 10;
        let mut longest_public = 9;

        for item in [&self.containers.items, &self.hidden_containers] {
            for item in item {
                longest_ip = longest_ip.max(
                    item.ports
                        .iter()
                        .map(ContainerPorts::len_ip)
                        .max()
                        .unwrap_or(3),
                );
                longest_private = longest_private.max(
                    item.ports
                        .iter()
                        .map(ContainerPorts::len_private)
                        .max()
                        .unwrap_or(8),
                );
                longest_public = longest_public.max(
                    item.ports
                        .iter()
                        .map(ContainerPorts::len_public)
                        .max()
                        .unwrap_or(6),
                );
            }
        }

        (longest_ip, longest_private, longest_public)
    }

    /// Get Option of the current selected container's ports, sorted by private port
    pub fn get_selected_ports(&mut self) -> Option<(Vec<ContainerPorts>, State)> {
        if let Some(item) = self.get_mut_selected_container() {
            let mut ports = item.ports.clone();
            ports.sort_by(|a, b| a.private.cmp(&b.private));
            return Some((ports, item.state));
        }
        None
    }

    /// Get mutable Option of the current selected container
    fn get_mut_selected_container(&mut self) -> Option<&mut ContainerItem> {
        self.containers
            .state
            .selected()
            .and_then(|i| self.containers.items.get_mut(i))
    }

    /// Get a mutable container by given id
    fn get_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> {
        self.containers.items.iter_mut().find(|i| &i.id == id)
    }

    /// Get a mutable container by given id in the tmp_container vec
    fn get_hidden_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> {
        self.hidden_containers.iter_mut().find(|i| &i.id == id)
    }

    /// Get the ContainerName of by ID
    pub fn get_container_name_by_id(&mut self, id: &ContainerId) -> Option<ContainerName> {
        self.containers
            .items
            .iter_mut()
            .find(|i| &i.id == id)
            .map(|i| i.name.clone())
    }

    /// Find the id of the currently selected container.
    /// If any containers on system, will always return a ContainerId
    /// Only returns None when no containers found.
    pub fn get_selected_container_id(&self) -> Option<ContainerId> {
        self.get_selected_container().map(|i| i.id.clone())
    }

    /// Get the Id and State for the currently selected container - used by the exec check method
    pub fn get_selected_container_id_state_name(&self) -> Option<(ContainerId, State, String)> {
        self.get_selected_container()
            .map(|i| (i.id.clone(), i.state, i.name.get().to_owned()))
    }

    /// Selected DockerCommand methods

    /// Get the current selected docker command
    /// So know which command to execute
    pub fn selected_docker_controls(&self) -> Option<DockerControls> {
        self.get_selected_container().and_then(|i| {
            i.docker_controls.state.selected().and_then(|x| {
                i.docker_controls
                    .items
                    .get(x)
                    .map(std::borrow::ToOwned::to_owned)
            })
        })
    }

    /// Change selected choice of docker commands of selected container
    pub fn docker_controls_next(&mut self) {
        if let Some(i) = self.get_mut_selected_container() {
            i.docker_controls.next();
        }
    }

    /// Change selected choice of docker commands of selected container
    pub fn docker_controls_previous(&mut self) {
        if let Some(i) = self.get_mut_selected_container() {
            i.docker_controls.previous();
        }
    }

    /// Change selected choice of docker commands of selected container
    pub fn docker_controls_start(&mut self) {
        if let Some(i) = self.get_mut_selected_container() {
            i.docker_controls.start();
        }
    }

    /// Change selected choice of docker commands of selected container
    pub fn docker_controls_end(&mut self) {
        if let Some(i) = self.get_mut_selected_container() {
            i.docker_controls.end();
        }
    }

    /// Get mutable Option of the currently selected container DockerControls state
    pub fn get_control_state(&mut self) -> Option<&mut ListState> {
        self.get_mut_selected_container()
            .map(|i| &mut i.docker_controls.state)
    }

    /// Get mutable Option of the currently selected container DockerControls items
    /// TODO command or control, need a uniform name across the application
    pub fn get_control_items(&mut self) -> Option<&mut Vec<DockerControls>> {
        self.get_mut_selected_container()
            .map(|i| &mut i.docker_controls.items)
    }

    /// Logs related methods

    /// Get the title for log panel for selected container, will be either
    /// 1) "logs x/x - container_name - container_image"
    /// 2) "logs - container_name - container_image" when no logs found
    /// 3) "" no container currently selected - aka no containers on system
    pub fn get_log_title(&self) -> String {
        self.get_selected_container()
            .map_or_else(String::new, |ci| {
                let logs_len = ci.logs.get_state_title();
                let prefix = if logs_len.is_empty() {
                    String::from(" ")
                } else {
                    format!("{logs_len} ")
                };
                format!("{}- {} - {}", prefix, ci.name.get(), ci.image.get())
            })
    }

    /// select next selected log line
    pub fn log_next(&mut self) {
        if let Some(i) = self.get_mut_selected_container() {
            i.logs.next();
        }
    }

    /// select previous selected log line
    pub fn log_previous(&mut self) {
        if let Some(i) = self.get_mut_selected_container() {
            i.logs.previous();
        }
    }

    /// select last selected log line
    pub fn log_end(&mut self) {
        if let Some(i) = self.get_mut_selected_container() {
            i.logs.end();
        }
    }

    /// select first selected log line
    pub fn log_start(&mut self) {
        if let Some(i) = self.get_mut_selected_container() {
            i.logs.start();
        }
    }

    /// Get mutable Vec of current containers logs
    pub fn get_logs(&mut self) -> Vec<ListItem<'static>> {
        self.containers
            .state
            .selected()
            .and_then(|i| self.containers.items.get_mut(i))
            .map_or(vec![], |i| i.logs.to_vec())
    }

    /// Get mutable Option of the currently selected container Logs state
    pub fn get_log_state(&mut self) -> Option<&mut ListState> {
        self.containers
            .state
            .selected()
            .and_then(|i| self.containers.items.get_mut(i))
            .map(|i| i.logs.state())
    }

    /// Chart data related methods

    /// Get mutable Option of the currently selected container chart data
    pub fn get_chart_data(&mut self) -> Option<(CpuTuple, MemTuple)> {
        self.containers
            .state
            .selected()
            .and_then(|i| self.containers.items.get_mut(i))
            .map(|i| i.get_chart_data())
    }

    /// Error related methods

    /// Get single app_state error
    pub const fn get_error(&self) -> Option<AppError> {
        self.error
    }

    /// Remove single app_state error
    pub fn remove_error(&mut self) {
        self.error = None;
    }

    /// Insert single app_state error
    pub fn set_error(&mut self, error: AppError, gui_state: &Arc<Mutex<GuiState>>, status: Status) {
        gui_state.lock().status_push(status);
        self.error = Some(error);
    }

    /// Check if the selected container is a dockerised version of oxker
    /// So that can disallow commands to be send
    /// Is a shabby way of implementing this
    pub fn is_oxker(&self) -> bool {
        self.get_selected_container().map_or(false, |i| i.is_oxker)
    }

    /// Check if selected container is oxker and also that oxker is being run in a container
    pub fn is_oxker_in_container(&self) -> bool {
        self.get_selected_container()
            .map_or(false, |i| i.is_oxker && self.args.in_container)
    }

    /// Find the widths for the strings in the containers panel.
    /// So can display nicely and evenly
    /// Searches in both contains & hidden_containers
    pub fn get_width(&self) -> Columns {
        let mut columns = Columns::new();
        let count = |x: &str| u8::try_from(x.chars().count()).unwrap_or(12);

        // Should probably find a refactor here somewhere
        for container in [&self.containers.items, &self.hidden_containers] {
            for container in container {
                let cpu_count = container.cpu_stats.back().map_or_else(
                    || count(&CpuStats::default().to_string()),
                    |i| count(&i.to_string()),
                );

                let mem_current_count = container.mem_stats.back().map_or_else(
                    || count(&ByteStats::default().to_string()),
                    |i| count(&i.to_string()),
                );

                columns.cpu.1 = columns.cpu.1.max(cpu_count);
                columns.image.1 = columns.image.1.max(count(&container.image.to_string()));
                columns.mem.1 = columns.mem.1.max(mem_current_count);
                columns.mem.2 = columns.mem.2.max(count(&container.mem_limit.to_string()));
                columns.name.1 = columns.name.1.max(count(&container.name.to_string()));
                columns.net_rx.1 = columns.net_rx.1.max(count(&container.rx.to_string()));
                columns.net_tx.1 = columns.net_tx.1.max(count(&container.tx.to_string()));
                columns.state.1 = columns.state.1.max(count(&container.state.to_string()));
                columns.status.1 = columns.status.1.max(count(container.status.get()));
            }
        }
        columns
    }

    /// Update related methods

    /// Get mutable reference to a container in the containers vec & the hidden_containers vec
    fn get_any_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> {
        if self.get_hidden_container_by_id(id).is_some() {
            self.get_hidden_container_by_id(id)
        } else {
            self.get_container_by_id(id)
        }
    }

    /// Update container mem, cpu, & network stats, in single function so only need to call .lock() once
    /// Will also, if a sort is set, sort the containers
    pub fn update_stats_by_id(
        &mut self,
        id: &ContainerId,
        cpu_stat: Option<f64>,
        mem_stat: Option<u64>,
        mem_limit: u64,
        rx: u64,
        tx: u64,
    ) {
        if let Some(container) = self.get_any_container_by_id(id) {
            if container.cpu_stats.len() >= 60 {
                container.cpu_stats.pop_front();
            }
            if container.mem_stats.len() >= 60 {
                container.mem_stats.pop_front();
            }

            if let Some(cpu) = cpu_stat {
                container.cpu_stats.push_back(CpuStats::new(cpu));
            }
            if let Some(mem) = mem_stat {
                container.mem_stats.push_back(ByteStats::new(mem));
            }

            container.rx.update(rx);
            container.tx.update(tx);
            container.mem_limit.update(mem_limit);
        }
        // need to benchmark this?
        self.sort_containers();
    }

    /// Update, or insert, containers
    pub fn update_containers(&mut self, all_containers: &mut [ContainerSummary]) {
        let all_ids = self
            .containers
            .items
            .iter()
            .map(|i| i.id.clone())
            .collect::<Vec<_>>();

        // Only sort it no containers currently set, as afterwards the order is fixed
        if self.containers.items.is_empty() {
            all_containers.sort_by(|a, b| a.created.cmp(&b.created));
        }

        if !all_containers.is_empty() && self.containers.state.selected().is_none() {
            self.containers.start();
        }

        for (index, id) in all_ids.iter().enumerate() {
            if !all_containers
                .iter()
                .filter_map(|i| i.id.as_ref())
                .any(|x| x == id.get())
            {
                // If removed container is currently selected, then change selected to previous
                // This will default to 0 in any edge cases
                if self.containers.state.selected().is_some() {
                    self.containers.previous();
                }
                // Check is some, else can cause out of bounds error, if containers get removed before a docker update
                if self.containers.items.get(index).is_some() {
                    self.containers.items.remove(index);
                }
            }
        }

        for i in all_containers {
            if let Some(id) = i.id.as_ref() {
                let name = i.names.as_mut().map_or(String::new(), |names| {
                    names.first_mut().map_or(String::new(), |f| {
                        if f.starts_with('/') {
                            f.remove(0);
                        }
                        (*f).to_string()
                    })
                });

                let ports = i.ports.as_ref().map_or(vec![], |i| {
                    i.iter().map(ContainerPorts::from).collect::<Vec<_>>()
                });

                let id = ContainerId::from(id.as_str());

                let is_oxker = i
                    .command
                    .as_ref()
                    .map_or(false, |i| i.starts_with(ENTRY_POINT));

                let status = ContainerStatus::from(
                    i.status
                        .as_ref()
                        .map_or(String::new(), std::clone::Clone::clone),
                );

                let state = State::from((i.state.as_ref().map_or("dead", |z| z), &status));
                let image = i
                    .image
                    .as_ref()
                    .map_or(String::new(), std::clone::Clone::clone);

                let created = i
                    .created
                    .map_or(0, |i| u64::try_from(i).unwrap_or_default());

                if let Some(item) = self.get_any_container_by_id(&id) {
                    if item.name.get() != name {
                        item.name.set(name);
                    };
                    if item.status != status {
                        item.status = status;
                    };
                    if item.state != state {
                        item.docker_controls.items = DockerControls::gen_vec(state);
                        // Update the list state, needs to be None if the gen_vec returns an empty vec
                        match state {
                            State::Removing | State::Restarting | State::Unknown => {
                                item.docker_controls.state.select(None);
                            }
                            _ => item.docker_controls.start(),
                        };
                        item.state = state;
                    };

                    item.ports = ports;

                    if item.image.get() != image {
                        item.image.set(image);
                    };
                } else {
                    // container not known, so make new ContainerItem and push into containers Ve
                    let container = ContainerItem::new(
                        created, id, image, is_oxker, name, ports, state, status,
                    );
                    let can_insert = self.can_insert(&container);
                    if can_insert {
                        self.containers.items.push(container);
                    } else {
                        self.hidden_containers.push(container);
                    }
                }
            }
        }
    }

    /// Update logs of a given container, based on id
    pub fn update_log_by_id(&mut self, logs: Vec<String>, id: &ContainerId) {
        let color = self.args.color;
        let raw = self.args.raw;

        let timestamp = self.args.timestamp;

        if let Some(container) = self.get_any_container_by_id(id) {
            if !container.is_oxker {
                container.last_updated = Self::get_systemtime();
                let current_len = container.logs.len();

                for mut i in logs {
                    let tz = LogsTz::from(i.as_str());
                    if !timestamp {
                        i = i.replace(&tz.to_string(), "");
                    }
                    let lines = if color {
                        log_sanitizer::colorize_logs(&i)
                    } else if raw {
                        log_sanitizer::raw(&i)
                    } else {
                        log_sanitizer::remove_ansi(&i)
                    };
                    container.logs.insert(ListItem::new(lines), tz);
                }

                // Set the logs selected row for each container
                // Either when no long currently selected, or currently selected (before updated) is already at end
                if container.logs.state().selected().is_none()
                    || container.logs.state().selected().map_or(1, |f| f + 1) == current_len
                {
                    container.logs.end();
                }
            }
        }
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {

    use super::*;
    use crate::tests::{gen_appdata, gen_container_summary, gen_containers};
    use std::collections::VecDeque;

    // ******* //
    // Sort by //
    // ******* //

    #[test]
    /// Sort by header: name
    fn test_app_data_set_sort_by_header_name() {
        let (_ids, containers) = gen_containers();

        let mut app_data = gen_appdata(&containers);

        let result = app_data.get_container_items();
        assert_eq!(result, &containers);

        // descending
        app_data.set_sorted(Some((Header::Name, SortedOrder::Desc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("3"));
        assert_eq!(b.id, ContainerId::from("2"));
        assert_eq!(c.id, ContainerId::from("1"));

        // ascending
        app_data.set_sorted(Some((Header::Name, SortedOrder::Asc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("1"));
        assert_eq!(b.id, ContainerId::from("2"));
        assert_eq!(c.id, ContainerId::from("3"));
    }

    #[test]
    /// Sort by header: state
    fn test_app_data_set_sort_by_header_state() {
        let (_ids, containers) = gen_containers();

        let mut app_data = gen_appdata(&containers);

        let result = app_data.get_container_items();
        assert_eq!(result, &containers);

        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) {
            i.state = State::Exited;
        }
        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) {
            i.state = State::Running(RunningState::Healthy);
        }
        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) {
            i.state = State::Paused;
        }

        // descending
        app_data.set_sorted(Some((Header::State, SortedOrder::Desc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("1"));
        assert_eq!(b.id, ContainerId::from("3"));
        assert_eq!(c.id, ContainerId::from("2"));

        // ascending
        app_data.set_sorted(Some((Header::State, SortedOrder::Asc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("2"));
        assert_eq!(b.id, ContainerId::from("3"));
        assert_eq!(c.id, ContainerId::from("1"));
    }

    #[test]
    /// Sort by header: status
    fn test_app_data_set_sort_by_header_status() {
        let (_ids, containers) = gen_containers();

        let mut app_data = gen_appdata(&containers);

        let result = app_data.get_container_items();
        assert_eq!(result, &containers);

        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) {
            ContainerStatus::from("Exited (0) 10 minutes ago".to_owned()).clone_into(&mut i.status);
        }

        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) {
            // "Up 2 hours (Paused)".clone_into(&mut i.status);
            ContainerStatus::from("Up 2 hours (Paused)".to_owned()).clone_into(&mut i.status);
        }

        // Sort by status
        // descending
        app_data.set_sorted(Some((Header::Status, SortedOrder::Desc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("3"));
        assert_eq!(b.id, ContainerId::from("1"));
        assert_eq!(c.id, ContainerId::from("2"));

        // ascending
        app_data.set_sorted(Some((Header::Status, SortedOrder::Asc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("2"));
        assert_eq!(b.id, ContainerId::from("1"));
        assert_eq!(c.id, ContainerId::from("3"));
    }

    #[test]
    /// Sort by header: cpu
    fn test_app_data_set_sort_by_header_cpu() {
        let (_ids, containers) = gen_containers();

        let mut app_data = gen_appdata(&containers);

        let result = app_data.get_container_items();
        assert_eq!(result, &containers);

        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) {
            i.cpu_stats = VecDeque::from([CpuStats::new(10.1)]);
        }
        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) {
            i.cpu_stats = VecDeque::from([CpuStats::new(8.1)]);
        }
        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) {
            i.cpu_stats = VecDeque::from([CpuStats::new(20.3)]);
        }

        // descending
        app_data.set_sorted(Some((Header::Cpu, SortedOrder::Desc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("3"));
        assert_eq!(b.id, ContainerId::from("1"));
        assert_eq!(c.id, ContainerId::from("2"));

        // ascending
        app_data.set_sorted(Some((Header::Cpu, SortedOrder::Asc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("2"));
        assert_eq!(b.id, ContainerId::from("1"));
        assert_eq!(c.id, ContainerId::from("3"));
    }

    #[test]
    /// Sort by header: memory
    fn test_app_data_set_sort_by_header_mem() {
        let (_ids, containers) = gen_containers();

        let mut app_data = gen_appdata(&containers);

        let result = app_data.get_container_items();
        assert_eq!(result, &containers);

        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) {
            i.mem_stats = VecDeque::from([ByteStats::new(40)]);
        }
        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) {
            i.mem_stats = VecDeque::from([ByteStats::new(80)]);
        }
        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) {
            i.mem_stats = VecDeque::from([ByteStats::new(2)]);
        }

        // descending
        app_data.set_sorted(Some((Header::Memory, SortedOrder::Desc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("2"));
        assert_eq!(b.id, ContainerId::from("1"));
        assert_eq!(c.id, ContainerId::from("3"));

        // ascending
        app_data.set_sorted(Some((Header::Memory, SortedOrder::Asc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("3"));
        assert_eq!(b.id, ContainerId::from("1"));
        assert_eq!(c.id, ContainerId::from("2"));
    }

    #[test]
    /// Sort by header: id
    fn test_app_data_set_sort_by_header_id() {
        let (_ids, containers) = gen_containers();

        let mut app_data = gen_appdata(&containers);

        let result = app_data.get_container_items();
        assert_eq!(result, &containers);

        // descending
        app_data.set_sorted(Some((Header::Id, SortedOrder::Desc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("3"));
        assert_eq!(b.id, ContainerId::from("2"));
        assert_eq!(c.id, ContainerId::from("1"));

        // ascending
        app_data.set_sorted(Some((Header::Id, SortedOrder::Asc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("1"));
        assert_eq!(b.id, ContainerId::from("2"));
        assert_eq!(c.id, ContainerId::from("3"));
    }

    #[test]
    /// Sort by header: image
    fn test_app_data_set_sort_by_header_image() {
        let (_ids, containers) = gen_containers();

        let mut app_data = gen_appdata(&containers);

        let result = app_data.get_container_items();
        assert_eq!(result, &containers);

        // descending
        app_data.set_sorted(Some((Header::Image, SortedOrder::Desc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("3"));
        assert_eq!(b.id, ContainerId::from("2"));
        assert_eq!(c.id, ContainerId::from("1"));

        // ascending
        app_data.set_sorted(Some((Header::Image, SortedOrder::Asc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("1"));
        assert_eq!(b.id, ContainerId::from("2"));
        assert_eq!(c.id, ContainerId::from("3"));
    }

    #[test]
    /// Sort by header: rx
    fn test_app_data_set_sort_by_header_rx() {
        let (_ids, containers) = gen_containers();

        let mut app_data = gen_appdata(&containers);

        let result = app_data.get_container_items();
        assert_eq!(result, &containers);

        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) {
            i.rx = ByteStats::new(40);
        }
        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) {
            i.rx = ByteStats::new(80);
        }
        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) {
            i.rx = ByteStats::new(2);
        }

        // descending
        app_data.set_sorted(Some((Header::Rx, SortedOrder::Desc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("2"));
        assert_eq!(b.id, ContainerId::from("1"));
        assert_eq!(c.id, ContainerId::from("3"));

        // ascending
        app_data.set_sorted(Some((Header::Rx, SortedOrder::Asc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("3"));
        assert_eq!(b.id, ContainerId::from("1"));
        assert_eq!(c.id, ContainerId::from("2"));
    }

    #[test]
    /// Sort by header: tx
    fn test_app_data_set_sort_by_header_tx() {
        let (_ids, containers) = gen_containers();

        let mut app_data = gen_appdata(&containers);

        let result = app_data.get_container_items();
        assert_eq!(result, &containers);

        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) {
            i.rx = ByteStats::new(400);
        }
        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) {
            i.rx = ByteStats::new(80);
        }
        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) {
            i.rx = ByteStats::new(83);
        }

        // descending
        app_data.set_sorted(Some((Header::Rx, SortedOrder::Desc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("1"));
        assert_eq!(b.id, ContainerId::from("3"));
        assert_eq!(c.id, ContainerId::from("2"));

        // ascending
        app_data.set_sorted(Some((Header::Rx, SortedOrder::Asc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("2"));
        assert_eq!(b.id, ContainerId::from("3"));
        assert_eq!(c.id, ContainerId::from("1"));
    }

    #[test]
    /// Sort by header when selected headers match
    fn test_app_data_set_sort_by_header_match() {
        let (_ids, containers) = gen_containers();

        let mut app_data = gen_appdata(&containers);

        let result = app_data.get_container_items();
        assert_eq!(result, &containers);

        // descending
        app_data.set_sorted(Some((Header::Rx, SortedOrder::Desc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("3"));
        assert_eq!(b.id, ContainerId::from("2"));
        assert_eq!(c.id, ContainerId::from("1"));

        // ascending
        app_data.set_sorted(Some((Header::Rx, SortedOrder::Asc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("1"));
        assert_eq!(b.id, ContainerId::from("2"));
        assert_eq!(c.id, ContainerId::from("3"));
    }

    #[test]
    /// reset sorted
    fn test_app_data_reset_sorted() {
        let (_ids, containers) = gen_containers();

        let mut app_data = gen_appdata(&containers);

        let result = app_data.get_container_items();
        assert_eq!(result, &containers);

        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) {
            i.rx = ByteStats::new(400);
        }
        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) {
            i.rx = ByteStats::new(80);
        }
        if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) {
            i.rx = ByteStats::new(83);
        }

        app_data.set_sorted(Some((Header::Rx, SortedOrder::Asc)));
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("2"));
        assert_eq!(b.id, ContainerId::from("3"));
        assert_eq!(c.id, ContainerId::from("1"));

        app_data.set_sorted(None);
        let result = app_data.get_container_items();
        let (a, b, c) = (&result[0], &result[1], &result[2]);
        assert_eq!(a.id, ContainerId::from("1"));
        assert_eq!(b.id, ContainerId::from("2"));
        assert_eq!(c.id, ContainerId::from("3"));
    }

    // **************** //
    // Container state  //
    // **************** //

    #[test]
    /// Get len of current containers vec
    fn test_app_data_get_container_len() {
        let (_ids, containers) = gen_containers();
        let app_data = gen_appdata(&containers);
        assert_eq!(app_data.get_container_len(), 3);
    }

    #[test]
    /// Select the first container
    fn test_app_data_containers_start() {
        let (_ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);

        // No container selected
        let result = app_data.get_container_state();
        assert_eq!(result.selected(), None);
        assert_eq!(result.offset(), 0);

        // First container selected
        app_data.containers_start();
        let result = app_data.get_container_state();
        assert_eq!(result.selected(), Some(0));
        assert_eq!(result.offset(), 0);

        let result = app_data.get_selected_container_id();
        assert_eq!(result, Some(ContainerId::from("1")));
        let result = app_data.get_selected_container_id_state_name();
        assert_eq!(
            result,
            Some((
                ContainerId::from("1"),
                State::Running(RunningState::Healthy),
                "container_1".to_owned()
            ))
        );

        // Calling previous when at start has no effect
        app_data.containers_previous();
        let result = app_data.get_selected_container_id();
        assert_eq!(result, Some(ContainerId::from("1")));
        let result = app_data.get_selected_container_id_state_name();
        assert_eq!(
            result,
            Some((
                ContainerId::from("1"),
                State::Running(RunningState::Healthy),
                "container_1".to_owned()
            ))
        );
    }

    #[test]
    /// advance container list state by one
    /// get get_selected_container_id() & get_selected_container_id_state_name() return valid Some data
    fn test_app_data_containers_next() {
        let (_ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);

        // Advance list state by 1
        app_data.containers_start();
        app_data.containers_next();

        let result = app_data.get_container_state();
        assert_eq!(result.selected(), Some(1));
        assert_eq!(result.offset(), 0);

        let result = app_data.get_selected_container_id();
        assert_eq!(result, Some(ContainerId::from("2")));
        let result = app_data.get_selected_container_id_state_name();
        assert_eq!(
            result,
            Some((
                ContainerId::from("2"),
                State::Running(RunningState::Healthy),
                "container_2".to_owned()
            ))
        );
    }

    #[test]
    /// advance container list state to the end
    /// get get_selected_container_id() & get_selected_container_id_state_name() return valid Some data
    fn test_app_data_containers_end() {
        let (_ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);

        app_data.containers_end();
        let result = app_data.get_container_state();
        assert_eq!(result.selected(), Some(2));
        assert_eq!(result.offset(), 0);

        let result = app_data.get_selected_container_id();
        assert_eq!(result, Some(ContainerId::from("3")));
        let result = app_data.get_selected_container_id_state_name();
        assert_eq!(
            result,
            Some((
                ContainerId::from("3"),
                State::Running(RunningState::Healthy),
                "container_3".to_owned()
            ))
        );

        // Calling previous when at end has no effect
        app_data.containers_next();
        let result = app_data.get_selected_container_id();
        assert_eq!(result, Some(ContainerId::from("3")));
        let result = app_data.get_selected_container_id_state_name();
        assert_eq!(
            result,
            Some((
                ContainerId::from("3"),
                State::Running(RunningState::Healthy),
                "container_3".to_owned()
            ))
        );
    }

    #[test]
    /// go to previous container
    fn test_app_data_containers_prev() {
        let (_ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);

        app_data.containers_end();
        app_data.containers_previous();
        let result = app_data.get_container_state();
        assert_eq!(result.selected(), Some(1));
        assert_eq!(result.offset(), 0);
    }

    #[test]
    /// Get the currently selected container
    fn test_app_data_get_selected_container() {
        let (_ids, mut containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);

        let result = app_data.get_selected_container();
        assert_eq!(result, None);

        app_data.containers.start();
        app_data.containers.next();

        let result = app_data.get_selected_container();
        assert_eq!(result, Some(&containers[1]));

        // As above, but now as mut
        let result = app_data.get_mut_selected_container();
        assert_eq!(result, Some(&mut containers[1]));
    }

    #[test]
    /// Get mut container by id
    fn test_app_data_get_container_by_id() {
        let (_ids, mut containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);

        let result = app_data.get_container_by_id(&ContainerId::from("2"));
        assert_eq!(result, Some(&mut containers[1]));
    }

    #[test]
    /// Get just the containers name by id
    fn test_app_data_get_container_name_by_id() {
        let (_ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);

        let result = app_data.get_container_name_by_id(&ContainerId::from("2"));
        assert_eq!(result, Some(ContainerName::from("container_2")));
    }

    #[test]
    /// Get the id of the currently selected container
    fn test_app_data_get_selected_container_id() {
        let (_ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);
        app_data.containers_end();

        let result = app_data.get_selected_container_id();
        assert_eq!(result, Some(ContainerId::from("3")));
    }

    #[test]
    fn test_app_data_get_selected_container_id_state_name() {
        let (_ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);
        app_data.containers_end();

        let result = app_data.get_selected_container_id_state_name();
        assert_eq!(
            result,
            Some((
                ContainerId::from("3"),
                State::Running(RunningState::Healthy),
                "container_3".to_owned()
            ))
        );
    }

    // ************** //
    // DockerControls //
    // ************** //

    #[test]
    /// Docker commands returned correctly
    fn test_app_data_selected_docker_command() {
        let (_ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);

        // No commands when no container selected
        let result = app_data.selected_docker_controls();
        assert!(result.is_none());

        // Correct commands returned
        app_data.containers_start();
        app_data.docker_controls_start();

        let result = app_data.selected_docker_controls();
        assert_eq!(result, Some(DockerControls::Pause));
    }

    #[test]
    /// Docker command next works
    fn test_app_data_selected_docker_command_next() {
        let (_ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);
        app_data.containers_start();
        app_data.docker_controls_start();
        app_data.docker_controls_next();

        let result = app_data.selected_docker_controls();
        assert_eq!(result, Some(DockerControls::Restart));
    }

    #[test]
    /// Dockercommand end works, and next has no effect when at end
    fn test_app_data_selected_docker_command_end() {
        let (_ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);
        app_data.containers_start();
        app_data.docker_controls_end();

        let result = app_data.selected_docker_controls();
        assert_eq!(result, Some(DockerControls::Delete));

        // Next has no effect when at end
        app_data.docker_controls_next();
        let result = app_data.selected_docker_controls();
        assert_eq!(result, Some(DockerControls::Delete));
    }

    #[test]
    /// Docker commands previous works, and has no effect when at start
    fn test_app_data_selected_docker_command_previous() {
        let (_ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);
        app_data.containers_start();
        app_data.docker_controls_end();
        app_data.docker_controls_previous();

        let result = app_data.selected_docker_controls();
        assert_eq!(result, Some(DockerControls::Stop));

        // previous has no effect when at start
        app_data.docker_controls_start();
        app_data.docker_controls_previous();
        let result = app_data.selected_docker_controls();
        assert_eq!(result, Some(DockerControls::Pause));
    }

    #[test]
    /// DockerCommands get correct controls dependant on container state
    fn test_app_data_get_control_items() {
        let test_state = |state: State, expected: &mut Vec<DockerControls>| {
            let gen_item_state = |state: State| {
                ContainerItem::new(
                    1,
                    ContainerId::from("1"),
                    "image_1".to_owned(),
                    false,
                    "container_1".to_owned(),
                    vec![],
                    state,
                    ContainerStatus::from("Up 1 hour".to_owned()),
                )
            };
            let mut app_data = gen_appdata(&[gen_item_state(state)]);
            app_data.containers_start();
            app_data.docker_controls_start();

            let result = app_data.get_control_items();
            assert_eq!(result, Some(expected));
        };

        test_state(
            State::Dead,
            &mut vec![
                DockerControls::Start,
                DockerControls::Restart,
                DockerControls::Delete,
            ],
        );
        test_state(
            State::Exited,
            &mut vec![
                DockerControls::Start,
                DockerControls::Restart,
                DockerControls::Delete,
            ],
        );
        test_state(
            State::Paused,
            &mut vec![
                DockerControls::Resume,
                DockerControls::Stop,
                DockerControls::Delete,
            ],
        );
        test_state(State::Removing, &mut vec![DockerControls::Delete]);
        test_state(
            State::Restarting,
            &mut vec![DockerControls::Stop, DockerControls::Delete],
        );
        test_state(
            State::Running(RunningState::Healthy),
            &mut vec![
                DockerControls::Pause,
                DockerControls::Restart,
                DockerControls::Stop,
                DockerControls::Delete,
            ],
        );
        test_state(State::Unknown, &mut vec![DockerControls::Delete]);
    }

    // ****** //
    // Filter //
    // ****** //

    #[test]
    /// Data is filtered correctly by name
    fn test_app_data_filter_by_name() {
        let (_, containers) = gen_containers();

        let mut app_data = gen_appdata(&containers);

        assert!(app_data.get_filter_term().is_none());

        let pre_len = app_data.containers.items.len();
        app_data.filter_term_push('_');
        app_data.filter_term_push('2');

        assert_eq!(app_data.get_filter_term(), Some(&"_2".to_string()));

        app_data.filter_containers();
        let post_len = app_data.containers.items.len();
        assert!(pre_len != post_len);
        assert_eq!(post_len, 1);

        // Can insert checks against the current filter term
        assert!(app_data.can_insert(&containers[1]));
        assert!(!app_data.can_insert(&containers[0]));
        assert!(!app_data.can_insert(&containers[2]));
    }

    #[test]
    /// Data is filtered correctly by image
    fn test_app_data_filter_by_image() {
        let (_, containers) = gen_containers();

        let mut app_data = gen_appdata(&containers);

        assert!(app_data.get_filter_term().is_none());

        let pre_len = app_data.containers.items.len();
        for c in ['i', 'm', 'a', 'g', 'e', '_', '2'] {
            app_data.filter_term_push(c);
        }
        // app_data.filter_term_push('2');
        app_data.filter_by_next();

        assert_eq!(app_data.get_filter_by(), FilterBy::Image);
        assert_eq!(app_data.get_filter_term(), Some(&"image_2".to_string()));

        app_data.filter_containers();
        let post_len = app_data.containers.items.len();
        assert!(pre_len != post_len);
        assert_eq!(post_len, 1);

        assert!(!app_data.can_insert(&containers[0]));
        assert!(app_data.can_insert(&containers[1]));
        assert!(!app_data.can_insert(&containers[2]));
    }

    #[test]
    /// Data is filtered correctly by status
    fn test_app_data_filter_by_status() {
        let (_, mut containers) = gen_containers();
        ContainerStatus::from("Exited".to_owned()).clone_into(&mut containers[0].status);
        let mut app_data = gen_appdata(&containers);

        assert!(app_data.get_filter_term().is_none());

        let pre_len = app_data.containers.items.len();
        app_data.filter_term_push('x');

        app_data.filter_by_next();
        app_data.filter_by_next();

        assert_eq!(app_data.get_filter_by(), FilterBy::Status);
        assert_eq!(app_data.get_filter_term(), Some(&"x".to_string()));

        app_data.filter_containers();
        let post_len = app_data.containers.items.len();
        assert!(pre_len != post_len);
        assert_eq!(post_len, 1);

        assert!(app_data.can_insert(&containers[0]));
        assert!(!app_data.can_insert(&containers[1]));
        assert!(!app_data.can_insert(&containers[2]));
    }

    #[test]
    /// Data is filtered correctly by all
    fn test_app_data_filter_by_all() {
        let (_, mut containers) = gen_containers();
        ContainerStatus::from("Exited".to_owned()).clone_into(&mut containers[0].status);
        let mut app_data = gen_appdata(&containers);

        assert!(app_data.get_filter_term().is_none());

        let pre_len = app_data.containers.items.len();
        app_data.filter_term_push('x');

        app_data.filter_by_next();
        app_data.filter_by_next();
        app_data.filter_by_next();

        assert_eq!(app_data.get_filter_by(), FilterBy::All);
        assert_eq!(app_data.get_filter_term(), Some(&"x".to_string()));

        app_data.filter_containers();
        let post_len = app_data.containers.items.len();
        assert!(pre_len != post_len);
        assert_eq!(post_len, 1);

        assert!(app_data.can_insert(&containers[0]));
        assert!(!app_data.can_insert(&containers[1]));
        assert!(!app_data.can_insert(&containers[2]));
    }

    #[test]
    /// Data is filtered correctly after various next() and previous() commands
    fn test_app_data_filter_prev() {
        let (_, mut containers) = gen_containers();
        ContainerStatus::from("Exited".to_owned()).clone_into(&mut containers[0].status);
        let mut app_data = gen_appdata(&containers);

        assert!(app_data.get_filter_term().is_none());

        let pre_len = app_data.containers.items.len();
        app_data.filter_term_push('x');

        app_data.filter_by_next();
        app_data.filter_by_next();

        assert_eq!(app_data.get_filter_by(), FilterBy::Status);
        assert_eq!(app_data.get_filter_term(), Some(&"x".to_string()));

        app_data.filter_containers();
        let post_len = app_data.containers.items.len();
        assert!(pre_len != post_len);
        assert_eq!(post_len, 1);

        assert!(app_data.can_insert(&containers[0]));
        assert!(!app_data.can_insert(&containers[1]));
        assert!(!app_data.can_insert(&containers[2]));

        app_data.filter_by_prev();
        assert_eq!(app_data.get_filter_by(), FilterBy::Image);
        assert_eq!(app_data.get_filter_term(), Some(&"x".to_string()));

        app_data.filter_containers();
        let post_len = app_data.containers.items.len();
        assert!(pre_len != post_len);
        assert_eq!(post_len, 0);

        assert!(!app_data.can_insert(&containers[0]));
        assert!(!app_data.can_insert(&containers[1]));
        assert!(!app_data.can_insert(&containers[2]));
    }

    // **** //
    // Logs //
    // **** //

    #[test]
    /// log title string generated correctly
    fn test_app_data_get_log_title() {
        let (ids, containers) = gen_containers();

        let mut app_data = gen_appdata(&containers);

        // No container selected select
        let result = app_data.get_log_title();
        assert_eq!(result, "");

        // No logs
        app_data.containers.start();
        let result = app_data.get_log_title();
        assert_eq!(result, " - container_1 - image_1");

        // On last line of logs
        let logs = (1..=3).map(|i| format!("{i}")).collect::<Vec<_>>();
        app_data.update_log_by_id(logs, &ids[0]);
        let result = app_data.get_log_title();
        assert_eq!(result, " 3/3 - container_1 - image_1");

        // Change log state to no longer be at the end
        app_data.log_previous();
        let result = app_data.get_log_title();
        assert_eq!(result, " 2/3 - container_1 - image_1");
    }

    #[test]
    /// log title string generated correctly after container change
    fn test_app_data_get_log_title_after_container_change() {
        let (ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);

        // No container selected select
        let result = app_data.get_log_title();
        assert_eq!(result, "");

        app_data.containers_start();

        let result = app_data.get_log_title();
        assert_eq!(result, " - container_1 - image_1");

        // change container
        app_data.containers_next();
        let result = app_data.get_log_title();
        assert_eq!(result, " - container_2 - image_2");

        // On last line of logs
        let logs = (1..=3).map(|i| format!("{i}")).collect::<Vec<_>>();
        app_data.update_log_by_id(logs, &ids[1]);
        let result = app_data.get_log_title();
        assert_eq!(result, " 3/3 - container_2 - image_2");

        // Change log state to no longer be at the end
        app_data.log_previous();
        let result = app_data.get_log_title();
        assert_eq!(result, " 2/3 - container_2 - image_2");
    }

    #[test]
    /// update logs by id works
    fn test_app_data_update_log_by_id() {
        let (ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);

        // No container selected select
        let result = app_data.get_log_title();
        assert_eq!(result, "");

        app_data.containers_start();
        let logs = (1..=3).map(|i| format!("{i} {i}")).collect::<Vec<_>>();

        app_data.update_log_by_id(logs, &ids[0]);
        // app_data.log_start();

        let result = app_data.get_log_state();
        assert!(result.is_some());
        assert_eq!(result.as_ref().unwrap().selected(), Some(2));
        assert_eq!(result.unwrap().offset(), 0);

        let result = app_data.get_logs();
        assert_eq!(result.len(), 3);

        let result = app_data.get_log_title();
        assert_eq!(result, " 3/3 - container_1 - image_1");
    }

    #[test]
    /// logs state reset to start
    fn test_app_data_logs_start() {
        let (ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);
        let logs = (1..=3).map(|i| format!("{i} {i}")).collect::<Vec<_>>();
        app_data.containers_start();
        app_data.update_log_by_id(logs, &ids[0]);

        app_data.log_start();

        let result = app_data.get_log_state();
        assert!(result.is_some());
        assert_eq!(result.as_ref().unwrap().selected(), Some(0));
        assert_eq!(result.unwrap().offset(), 0);

        let result = app_data.get_log_title();
        assert_eq!(result, " 1/3 - container_1 - image_1");
    }

    #[test]
    /// logs state end goes to the end of the logs list
    fn test_app_data_logs_end() {
        let (ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);
        let logs = (1..=3).map(|i| format!("{i} {i}")).collect::<Vec<_>>();
        app_data.containers_start();
        app_data.update_log_by_id(logs, &ids[0]);

        app_data.log_start();

        let result = app_data.get_log_state();
        assert!(result.is_some());
        assert_eq!(result.as_ref().unwrap().selected(), Some(0));
        assert_eq!(result.unwrap().offset(), 0);

        let result = app_data.get_log_title();
        assert_eq!(result, " 1/3 - container_1 - image_1");

        app_data.log_end();
        let result = app_data.get_log_state();
        assert!(result.is_some());
        assert_eq!(result.as_ref().unwrap().selected(), Some(2));
        assert_eq!(result.unwrap().offset(), 0);

        let result = app_data.get_log_title();
        assert_eq!(result, " 3/3 - container_1 - image_1");
    }

    #[test]
    /// logs state next works
    /// At end has no effect
    fn test_app_data_logs_next() {
        let (ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);
        let logs = (1..=3).map(|i| format!("{i} {i}")).collect::<Vec<_>>();
        app_data.containers_start();
        app_data.update_log_by_id(logs, &ids[0]);

        app_data.log_start();

        let result = app_data.get_log_state();
        assert!(result.is_some());
        assert_eq!(result.as_ref().unwrap().selected(), Some(0));
        assert_eq!(result.unwrap().offset(), 0);

        let result = app_data.get_log_title();
        assert_eq!(result, " 1/3 - container_1 - image_1");

        app_data.log_next();

        let result = app_data.get_log_state();
        assert!(result.is_some());
        assert_eq!(result.as_ref().unwrap().selected(), Some(1));
        assert_eq!(result.unwrap().offset(), 0);

        let result = app_data.get_log_title();
        assert_eq!(result, " 2/3 - container_1 - image_1");

        app_data.log_next();
        let result = app_data.get_log_state();
        assert!(result.is_some());
        assert_eq!(result.as_ref().unwrap().selected(), Some(2));
        assert_eq!(result.unwrap().offset(), 0);

        let result = app_data.get_log_title();
        assert_eq!(result, " 3/3 - container_1 - image_1");
        app_data.log_next();

        let result = app_data.get_log_state();
        assert!(result.is_some());
        assert_eq!(result.as_ref().unwrap().selected(), Some(2));
        assert_eq!(result.unwrap().offset(), 0);

        let result = app_data.get_log_title();
        assert_eq!(result, " 3/3 - container_1 - image_1");
    }

    #[test]
    /// logs state previous works
    /// previous at start has no effect
    fn test_app_data_logs_previous() {
        let (ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);
        let logs = (1..=3).map(|i| format!("{i} {i}")).collect::<Vec<_>>();
        app_data.containers_start();
        app_data.update_log_by_id(logs, &ids[0]);

        app_data.log_end();

        let result = app_data.get_log_state();
        assert!(result.is_some());
        assert_eq!(result.as_ref().unwrap().selected(), Some(2));
        assert_eq!(result.unwrap().offset(), 0);

        let result = app_data.get_log_title();
        assert_eq!(result, " 3/3 - container_1 - image_1");

        app_data.log_previous();

        let result = app_data.get_log_state();
        assert!(result.is_some());
        assert_eq!(result.as_ref().unwrap().selected(), Some(1));
        assert_eq!(result.unwrap().offset(), 0);
        let result = app_data.get_log_title();
        assert_eq!(result, " 2/3 - container_1 - image_1");

        app_data.log_previous();
        let result = app_data.get_log_state();
        assert!(result.is_some());
        assert_eq!(result.as_ref().unwrap().selected(), Some(0));
        assert_eq!(result.unwrap().offset(), 0);
        let result = app_data.get_log_title();
        assert_eq!(result, " 1/3 - container_1 - image_1");

        app_data.log_previous();
        let result = app_data.get_log_state();
        assert!(result.is_some());
        assert_eq!(result.as_ref().unwrap().selected(), Some(0));
        assert_eq!(result.unwrap().offset(), 0);
        let result = app_data.get_log_title();
        assert_eq!(result, " 1/3 - container_1 - image_1");
    }

    // ********** //
    // Chart data //
    // ********** //

    #[test]
    /// Chart data returned correctly
    fn test_app_data_get_chart_data() {
        let (_ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);

        let result = app_data.get_chart_data();
        assert!(result.is_none());

        app_data.containers_start();

        if let Some(item) = app_data.get_container_by_id(&ContainerId::from("1")) {
            item.cpu_stats = VecDeque::from([CpuStats::new(1.1), CpuStats::new(1.2)]);
            item.mem_stats = VecDeque::from([ByteStats::new(1), ByteStats::new(2)]);
        }

        let result = app_data.get_chart_data();
        assert_eq!(
            result,
            Some((
                (
                    vec![(0.0, 1.1), (1.0, 1.2)],
                    CpuStats::new(1.2),
                    State::Running(RunningState::Healthy),
                ),
                (
                    vec![(0.0, 1.0), (1.0, 2.0)],
                    ByteStats::new(2),
                    State::Running(RunningState::Healthy),
                )
            ))
        );
    }

    // ************* //
    // Header Widths //
    // ************* //

    #[test]
    /// Header widths return correctly
    fn test_app_data_get_width() {
        let (_ids, containers) = gen_containers();
        let app_data = gen_appdata(&containers);

        let result = app_data.get_width();
        let expected = Columns {
            name: (Header::Name, 11),
            state: (Header::State, 9),
            status: (Header::Status, 9),
            cpu: (Header::Cpu, 6),
            mem: (Header::Memory, 7, 7),
            id: (Header::Id, 8),
            image: (Header::Image, 7),
            net_rx: (Header::Rx, 7),
            net_tx: (Header::Tx, 7),
        };
        assert_eq!(result, expected);
    }

    #[test]
    /// Header widths return correctly when some containers hidden
    fn test_app_data_get_width_filtered() {
        let (_ids, mut containers) = gen_containers();
        containers[0].name = ContainerName::from("some_longer_name_with_filter");
        let mut app_data = gen_appdata(&containers);

        let result = app_data.get_width();
        let expected = Columns {
            name: (Header::Name, 28),
            state: (Header::State, 9),
            status: (Header::Status, 9),
            cpu: (Header::Cpu, 6),
            mem: (Header::Memory, 7, 7),
            id: (Header::Id, 8),
            image: (Header::Image, 7),
            net_rx: (Header::Rx, 7),
            net_tx: (Header::Tx, 7),
        };

        assert_eq!(result, expected);
        app_data.filter_term_push('c');
        app_data.filter_containers();
        assert_eq!(result, expected);
    }

    // ***** //
    // Ports //
    // ***** //

    #[test]
    /// Returns selected containers ports ordered by private ip
    fn test_app_data_get_selected_ports() {
        let (_ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);

        app_data.containers.items[0].ports.push(ContainerPorts {
            ip: None,
            private: 10,
            public: Some(1),
        });
        app_data.containers.items[0].ports.push(ContainerPorts {
            ip: None,
            private: 11,
            public: Some(3),
        });
        app_data.containers.items[0].ports.push(ContainerPorts {
            ip: None,
            private: 4,
            public: Some(2),
        });

        // No containers selected
        let result = app_data.get_selected_ports();
        assert!(result.is_none());

        // Selected container & ports
        app_data.containers_start();
        let result = app_data.get_selected_ports();

        assert_eq!(
            result,
            Some((
                vec![
                    ContainerPorts {
                        ip: None,
                        private: 4,
                        public: Some(2)
                    },
                    ContainerPorts {
                        ip: None,
                        private: 10,
                        public: Some(1)
                    },
                    ContainerPorts {
                        ip: None,
                        private: 11,
                        public: Some(3)
                    },
                    ContainerPorts {
                        ip: None,
                        private: 8001,
                        public: None
                    }
                ],
                State::Running(RunningState::Healthy),
            ))
        );

        // Selected container & no ports
        app_data.containers_start();
        app_data.containers.items[0].ports = vec![];
        let result = app_data.get_selected_ports();

        assert_eq!(
            result,
            Some((vec![], State::Running(RunningState::Healthy)))
        );
    }

    // ************** //
    // Update mtehods //
    // ************** //

    #[test]
    /// Update stats functioning
    fn test_app_data_update_stats() {
        let (ids, containers) = gen_containers();

        let mut app_data = gen_appdata(&containers);

        let result = app_data.get_container_items();
        assert_eq!(result[0], containers[0]);

        app_data.update_stats_by_id(&ids[0], Some(10.0), Some(10), 10, 10, 10);

        let result = app_data.get_container_items();
        assert_ne!(result[0], containers[0]);
        assert_eq!(result[0].cpu_stats, VecDeque::from([CpuStats::new(10.0)]));
        assert_eq!(result[0].mem_stats, VecDeque::from([ByteStats::new(10)]));
        assert_eq!(result[0].mem_limit, ByteStats::new(10));
        assert_eq!(result[0].rx, ByteStats::new(10));
        assert_eq!(result[0].tx, ByteStats::new(10));
    }

    #[test]
    /// Update stats functioning
    fn test_app_data_update_containers() {
        let (_ids, containers) = gen_containers();
        let mut app_data = gen_appdata(&containers);
        let result_pre = app_data.get_container_items().to_owned();
        let mut input = [
            gen_container_summary(1, "paused"),
            gen_container_summary(2, "dead"),
        ];

        app_data.update_containers(&mut input);
        let result_post = app_data.get_container_items().to_owned();
        assert_ne!(result_pre, result_post);
        assert_eq!(result_post[0].state, State::Paused);
        assert_eq!(result_post[1].state, State::Dead);
    }

    #[test]
    /// Update logs don't work if container is_oxker: true
    fn test_app_data_update_log_by_id_is_oxker() {
        let (ids, mut containers) = gen_containers();
        containers[0].is_oxker = true;
        let mut app_data = gen_appdata(&containers);
        let logs = (1..=3).map(|i| format!("{i} {i}")).collect::<Vec<_>>();

        app_data.update_log_by_id(logs, &ids[0]);
        app_data.log_start();

        let result = app_data.get_log_state();
        assert!(result.is_none());
    }
}
