use async_trait::async_trait;
use chrono::{DateTime, Utc};
use k8s_openapi::api::core::v1::{
  Container, ContainerPort, ContainerState, ContainerStateWaiting, ContainerStatus, Pod, PodSpec,
  PodStatus,
};
use ratatui::{
  layout::Rect,
  style::Style,
  widgets::{Cell, Row},
  Frame,
};

use super::{
  key_binding::DEFAULT_KEYBINDING,
  models::{AppResource, KubeResource, Named},
  utils::{self, UNKNOWN},
  ActiveBlock, App,
};
use crate::{
  network::Network,
  ui::theme::Palette,
  ui::utils::{
    action_hint, copy_and_escape_title_line, copy_scroll_and_escape_title_line,
    describe_yaml_and_logs_hint, draw_describe_block, draw_resource_block, draw_yaml_block,
    get_describe_active, get_resource_title, help_bold_line, help_part, layout_block_top_border,
    loading, mixed_bold_line, responsive_columns, style_caution, style_failure, style_help,
    style_success, style_text, title_with_dual_style, wide_hint, ColumnDef, ResourceTableProps,
    ViewTier,
  },
};

#[derive(Clone, Default, Debug, PartialEq)]
pub struct KubePod {
  pub namespace: String,
  pub name: String,
  pub ready: (i32, i32),
  pub status: String,
  pub restarts: i32,
  pub cpu: String,
  pub mem: String,
  pub node: String,
  pub ip: String,
  pub age: String,
  pub containers: Vec<KubeContainer>,
  k8s_obj: Pod,
}

#[derive(Clone, Default, Debug, PartialEq)]
pub struct KubeContainer {
  pub name: String,
  pub image: String,
  pub ready: String,
  pub status: String,
  pub restarts: i32,
  pub liveliness_probe: bool,
  pub readiness_probe: bool,
  pub ports: String,
  pub age: String,
  pub pod_name: String,
  pub init: bool,
  k8s_obj: Option<Container>,
}

impl Named for KubeContainer {
  fn get_name(&self) -> &String {
    &self.name
  }
}

impl KubeResource<Option<Container>> for KubeContainer {
  fn get_k8s_obj(&self) -> &Option<Container> {
    &None
  }
}

impl From<Pod> for KubePod {
  fn from(pod: Pod) -> Self {
    Self::from_pod_at(pod, Utc::now())
  }
}

impl KubePod {
  fn from_pod_at(pod: Pod, now: DateTime<Utc>) -> Self {
    let age = utils::to_age(pod.metadata.creation_timestamp.as_ref(), now);
    let pod_name = pod.metadata.name.clone().unwrap_or_default();
    let main_containers = pod
      .spec
      .as_ref()
      .map(|spec| spec.containers.clone())
      .unwrap_or_default();
    let ready_total = main_containers.len() as i32;
    let (status, cr, restarts, ready_count, containers) = match &pod.status {
      Some(status) => {
        let (mut cr, mut rc) = (0, 0);
        let has_container_statuses = status.container_statuses.is_some();
        if let Some(c_stats) = status.container_statuses.as_ref() {
          c_stats.iter().for_each(|cs| {
            if cs.ready {
              cr += 1;
            }
            rc += cs.restart_count;
          });
        }

        let mut containers: Vec<KubeContainer> = main_containers
          .iter()
          .map(|c| {
            KubeContainer::from_api(
              c,
              pod_name.to_owned(),
              age.to_owned(),
              &status.container_statuses,
              false,
            )
          })
          .collect();

        let mut init_containers: Vec<KubeContainer> = pod
          .spec
          .as_ref()
          .unwrap_or(&PodSpec::default())
          .init_containers
          .as_ref()
          .unwrap_or(&vec![])
          .iter()
          .map(|c| {
            KubeContainer::from_api(
              c,
              pod_name.to_owned(),
              age.to_owned(),
              &status.init_container_statuses,
              true,
            )
          })
          .collect();

        // merge containers and init-containers into single array
        containers.append(&mut init_containers);

        let status_name = get_status(status, &pod);
        let ready_count = if has_container_statuses || !is_pending_like_status(&status_name) {
          status
            .container_statuses
            .as_ref()
            .map(|c_stats| c_stats.len() as i32)
            .unwrap_or_default()
        } else {
          ready_total
        };

        (status_name, cr, rc, ready_count, containers)
      }
      _ => (UNKNOWN.into(), 0, 0, 0, vec![]),
    };

    KubePod {
      name: pod_name,
      namespace: pod.metadata.namespace.clone().unwrap_or_default(),
      ready: (cr, ready_count),
      restarts,
      // TODO implement pod metrics
      cpu: String::default(),
      mem: String::default(),
      node: pod
        .spec
        .as_ref()
        .and_then(|s| s.node_name.clone())
        .unwrap_or_default(),
      ip: pod
        .status
        .as_ref()
        .and_then(|s| s.pod_ip.clone())
        .unwrap_or_default(),
      status,
      age,
      containers,
      k8s_obj: utils::sanitize_obj(pod),
    }
  }
}

impl Named for KubePod {
  fn get_name(&self) -> &String {
    &self.name
  }
}

impl KubeResource<Pod> for KubePod {
  fn get_k8s_obj(&self) -> &Pod {
    &self.k8s_obj
  }
}

static PODS_TITLE: &str = "Pods";
pub struct PodResource {}

#[async_trait]
impl AppResource for PodResource {
  fn render(block: ActiveBlock, f: &mut Frame<'_>, app: &mut App, area: Rect) {
    match block {
      ActiveBlock::Containers => draw_containers_block(f, app, area),
      ActiveBlock::Describe => draw_describe_block(
        f,
        app,
        area,
        title_with_dual_style(
          get_resource_title(
            app,
            PODS_TITLE,
            get_describe_active(block),
            app.data.pods.items.len(),
          ),
          copy_and_escape_title_line(PODS_TITLE, app.palette),
          app.palette,
        ),
      ),
      ActiveBlock::Yaml => draw_yaml_block(
        f,
        app,
        area,
        title_with_dual_style(
          get_resource_title(
            app,
            PODS_TITLE,
            get_describe_active(block),
            app.data.pods.items.len(),
          ),
          copy_and_escape_title_line(PODS_TITLE, app.palette),
          app.palette,
        ),
      ),
      ActiveBlock::Logs => draw_logs_block(f, app, area),
      ActiveBlock::Namespaces => Self::render(app.get_prev_route().active_block, f, app, area),
      _ => draw_block(f, app, area),
    }
  }

  async fn get_resource(nw: &Network<'_>) {
    let items: Vec<KubePod> = nw.get_namespaced_resources(Pod::into).await;

    let mut app = nw.app.lock().await;
    if app.data.selected.pod.is_some() {
      let containers = &items.iter().find_map(|pod| {
        if pod.name == app.data.selected.pod.clone().unwrap() {
          Some(&pod.containers)
        } else {
          None
        }
      });
      if containers.is_some() {
        app.data.containers.set_items(containers.unwrap().clone());
      }
    }
    app.data.pods.set_items(items);
  }
}

fn capitalize_first(s: &str) -> String {
  let mut chars = s.chars();
  match chars.next() {
    None => String::new(),
    Some(c) => c.to_uppercase().to_string() + chars.as_str(),
  }
}

pub(crate) fn draw_block_as_sub(f: &mut Frame<'_>, app: &mut App, area: Rect) {
  let is_loading = app.is_loading();
  let parent = app
    .data
    .selected
    .pod_selector_resource
    .as_deref()
    .map(capitalize_first)
    .unwrap_or_default();
  let base = format!("{} -> Pods", parent);
  let title = get_resource_title(app, &base, &String::new(), app.data.pods.items.len());

  let tier = ViewTier::from_width(area.width, app.wide_columns);
  let (headers, widths) = responsive_columns(&POD_COLUMNS, tier);

  draw_resource_block(
    f,
    area,
    ResourceTableProps {
      title,
      inline_help: help_bold_line(
        format!(
          "{} · {} · {} · {}:back ",
          action_hint("containers", DEFAULT_KEYBINDING.submit.key),
          describe_yaml_and_logs_hint().trim_end(),
          wide_hint(),
          DEFAULT_KEYBINDING.esc.key.symbol()
        ),
        app.palette,
      ),
      resource: &mut app.data.pods,
      table_headers: headers,
      column_widths: widths,
    },
    |c| {
      let style = get_resource_row_style(c.status.as_str(), c.ready, app.palette);
      let mut cells = vec![
        Cell::from(c.namespace.to_owned()),
        Cell::from(c.name.to_owned()),
        Cell::from(format!("{}/{}", c.ready.0, c.ready.1)),
        Cell::from(c.status.to_owned()),
        Cell::from(c.restarts.to_string()),
      ];
      if tier >= ViewTier::Standard {
        cells.push(Cell::from(c.node.to_owned()));
        cells.push(Cell::from(c.ip.to_owned()));
      }
      cells.push(Cell::from(c.age.to_owned()));
      Row::new(cells).style(style)
    },
    app.palette,
    is_loading,
  );
}

const POD_COLUMNS: [ColumnDef; 8] = [
  ColumnDef::all("Namespace", 25, 20, 20),
  ColumnDef::all("Name", 35, 25, 25),
  ColumnDef::all("Ready", 10, 8, 8),
  ColumnDef::all("Status", 10, 10, 10),
  ColumnDef::all("Restarts", 10, 7, 7),
  ColumnDef::standard("Node", 12, 12),
  ColumnDef::standard("IP", 10, 10),
  ColumnDef::all("Age", 10, 8, 8),
];

fn draw_block(f: &mut Frame<'_>, app: &mut App, area: Rect) {
  let is_loading = app.is_loading();
  let title = get_resource_title(app, PODS_TITLE, "", app.data.pods.items.len());

  let tier = ViewTier::from_width(area.width, app.wide_columns);
  let (headers, widths) = responsive_columns(&POD_COLUMNS, tier);

  draw_resource_block(
    f,
    area,
    ResourceTableProps {
      title,
      inline_help: help_bold_line(
        format!(
          "{} · {} · {}",
          action_hint("containers", DEFAULT_KEYBINDING.submit.key),
          describe_yaml_and_logs_hint(),
          wide_hint()
        ),
        app.palette,
      ),
      resource: &mut app.data.pods,
      table_headers: headers,
      column_widths: widths,
    },
    |c| {
      let style = get_resource_row_style(c.status.as_str(), c.ready, app.palette);
      let mut cells = vec![
        Cell::from(c.namespace.to_owned()),
        Cell::from(c.name.to_owned()),
        Cell::from(format!("{}/{}", c.ready.0, c.ready.1)),
        Cell::from(c.status.to_owned()),
        Cell::from(c.restarts.to_string()),
      ];
      if tier >= ViewTier::Standard {
        cells.push(Cell::from(c.node.to_owned()));
        cells.push(Cell::from(c.ip.to_owned()));
      }
      cells.push(Cell::from(c.age.to_owned()));
      Row::new(cells).style(style)
    },
    app.palette,
    is_loading,
  );
}

const CONTAINER_COLUMNS: [ColumnDef; 9] = [
  ColumnDef::all("Name", 20, 20, 20),
  ColumnDef::all("Image", 25, 25, 25),
  ColumnDef::all("Init", 5, 5, 5),
  ColumnDef::all("Ready", 5, 5, 5),
  ColumnDef::all("State", 10, 10, 10),
  ColumnDef::all("Restarts", 5, 5, 5),
  ColumnDef::all("Probes(L/R)", 10, 10, 10),
  ColumnDef::all("Ports", 10, 10, 10),
  ColumnDef::all("Age", 10, 10, 10),
];

pub(crate) fn draw_containers_block(f: &mut Frame<'_>, app: &mut App, area: Rect) {
  let is_loading = app.is_loading();
  let title = get_container_title(app, app.data.containers.items.len(), "");

  let (headers, widths) = responsive_columns(&CONTAINER_COLUMNS, ViewTier::Compact);

  draw_resource_block(
    f,
    area,
    ResourceTableProps {
      title,
      inline_help: mixed_bold_line(
        [
          help_part(format!(
            "{} · {} · {} · {} · ",
            action_hint("logs", DEFAULT_KEYBINDING.submit.key),
            action_hint("shell", DEFAULT_KEYBINDING.shell_exec.key),
            action_hint("prev logs", DEFAULT_KEYBINDING.previous_logs.key),
            action_hint("menu", DEFAULT_KEYBINDING.open_action_menu.key),
          )),
          help_part(format!("{}:back ", DEFAULT_KEYBINDING.esc.key.symbol())),
        ],
        app.palette,
      ),
      resource: &mut app.data.containers,
      table_headers: headers,
      column_widths: widths,
    },
    |c| {
      let style = get_resource_row_style(c.status.as_str(), (0, 0), app.palette);
      Row::new(vec![
        Cell::from(c.name.to_owned()),
        Cell::from(c.image.to_owned()),
        Cell::from(c.init.to_string()),
        Cell::from(c.ready.to_owned()),
        Cell::from(c.status.to_owned()),
        Cell::from(c.restarts.to_string()),
        Cell::from(format!("{}/{}", c.liveliness_probe, c.readiness_probe,)),
        Cell::from(c.ports.to_owned()),
        Cell::from(c.age.to_owned()),
      ])
      .style(style)
    },
    app.palette,
    is_loading,
  );
}

fn get_container_title<S: AsRef<str>>(app: &App, container_len: usize, suffix: S) -> String {
  let base = match &app.data.selected.pod_selector_resource {
    Some(resource) => format!("{} -> Pods", capitalize_first(resource)),
    None => PODS_TITLE.to_string(),
  };
  let suffix = format!("-> Containers [{}] {}", container_len, suffix.as_ref());
  get_resource_title(app, &base, &suffix, app.data.pods.items.len())
}

pub(crate) fn draw_logs_block(f: &mut Frame<'_>, app: &mut App, area: Rect) {
  let is_aggregate = app.data.logs.id.starts_with("agg:");

  let (title, hint) = if is_aggregate {
    let resource = app
      .data
      .selected
      .pod_selector_resource
      .as_deref()
      .map(capitalize_first)
      .unwrap_or_default();
    let agg_name = app.data.logs.id.strip_prefix("agg:").unwrap_or_default();
    (
      format!(" {} -> Logs ({}) ", resource, agg_name),
      help_bold_line(
        format!(
          "{} · {} · {}:back ",
          action_hint("copy", DEFAULT_KEYBINDING.copy_to_clipboard.key),
          action_hint(
            if app.log_auto_scroll {
              "pause scroll"
            } else {
              "resume scroll"
            },
            DEFAULT_KEYBINDING.log_auto_scroll.key
          ),
          DEFAULT_KEYBINDING.esc.key.symbol()
        ),
        app.palette,
      ),
    )
  } else {
    let selected_container = app.data.selected.container.clone();
    let container_name = selected_container.unwrap_or_default();
    let logs_label = if app.log_previous {
      format!("-> Logs ({} · previous) ", container_name)
    } else {
      format!("-> Logs ({}) ", container_name)
    };
    (
      get_container_title(app, app.data.containers.items.len(), logs_label),
      copy_scroll_and_escape_title_line("Containers", app.log_auto_scroll, app.palette),
    )
  };

  let title = title_with_dual_style(title, hint, app.palette);

  let block = layout_block_top_border(title, app.palette);

  let selected_container = app.data.selected.container.clone();
  let container_name = selected_container.unwrap_or_default();

  if container_name == app.data.logs.id || is_aggregate {
    app.data.logs.render_list(
      f,
      area,
      block,
      style_text(app.palette),
      app.log_auto_scroll,
      app.log_wrap,
    );
  } else {
    loading(f, block, area, app.is_loading(), app.palette);
  }
}

fn get_resource_row_style(status: &str, ready: (i32, i32), palette: Palette) -> Style {
  if status == "Running" && ready.0 == ready.1 {
    style_success(palette)
  } else if status == "Completed" || status == "Succeeded" {
    // Done & inactive — de-emphasize so it reads apart from active (green)
    // and in-progress (amber) rows.
    style_help(palette)
  } else if [
    "ContainerCreating",
    "PodInitializing",
    "Pending",
    "Initialized",
  ]
  .contains(&status)
  {
    style_caution(palette)
  } else {
    style_failure(palette)
  }
}

fn is_pending_like_status(status: &str) -> bool {
  [
    "ContainerCreating",
    "PodInitializing",
    "Pending",
    "Initialized",
  ]
  .contains(&status)
}

impl KubeContainer {
  pub fn from_api(
    container: &Container,
    pod_name: String,
    age: String,
    c_stats_ref: &Option<Vec<ContainerStatus>>,
    init: bool,
  ) -> Self {
    let (mut ready, mut status, mut restarts) = ("false".to_string(), "<none>".to_string(), 0);
    if let Some(c_stats) = c_stats_ref {
      if let Some(c_stat) = c_stats.iter().find(|cs| cs.name == container.name) {
        ready = c_stat.ready.to_string();
        status = get_container_state(c_stat.state.clone());
        restarts = c_stat.restart_count;
      }
    }

    KubeContainer {
      name: container.name.clone(),
      pod_name,
      image: container.image.clone().unwrap_or_default(),
      ready,
      status,
      restarts,
      liveliness_probe: container.liveness_probe.is_some(),
      readiness_probe: container.readiness_probe.is_some(),
      ports: get_container_ports(&container.ports).unwrap_or_default(),
      age,
      init,
      k8s_obj: None,
    }
  }
}

fn get_container_state(os: Option<ContainerState>) -> String {
  match os {
    Some(s) => {
      if let Some(sw) = s.waiting {
        sw.reason.unwrap_or_else(|| "Waiting".into())
      } else if let Some(st) = s.terminated {
        st.reason.unwrap_or_else(|| "Terminating".into())
      } else if s.running.is_some() {
        "Running".into()
      } else {
        "<none>".into()
      }
    }
    None => "<none>".into(),
  }
}

fn get_status(stat: &PodStatus, pod: &Pod) -> String {
  let status = match &stat.phase {
    Some(phase) => phase.to_owned(),
    _ => UNKNOWN.into(),
  };
  let status = match &stat.reason {
    Some(reason) => {
      if reason == "NodeLost" && pod.metadata.deletion_timestamp.is_some() {
        UNKNOWN.into()
      } else {
        reason.to_owned()
      }
    }
    None => status,
  };

  // get int container status
  let status = match &stat.init_container_statuses {
    Some(ics) => {
      for (i, cs) in ics.iter().enumerate() {
        let c_status = match &cs.state {
          Some(s) => {
            if let Some(st) = &s.terminated {
              if st.exit_code == 0 {
                "".into()
              } else if st.reason.as_ref().unwrap_or(&String::default()).is_empty() {
                format!("Init:{}", st.reason.as_ref().unwrap())
              } else if st.signal.unwrap_or_default() != 0 {
                format!("Init:Signal:{}", st.signal.unwrap())
              } else {
                format!("Init:ExitCode:{}", st.exit_code)
              }
            } else if is_pod_init(s.waiting.clone()) {
              format!(
                "Init:{}",
                s.waiting
                  .as_ref()
                  .unwrap()
                  .reason
                  .as_ref()
                  .unwrap_or(&String::default())
              )
            } else {
              format!(
                "Init:{}/{}",
                i,
                pod
                  .spec
                  .as_ref()
                  .and_then(|ps| ps.init_containers.as_ref().map(|pic| pic.len()))
                  .unwrap_or(0)
              )
            }
          }
          None => "".into(),
        };
        if !c_status.is_empty() {
          return c_status;
        }
      }
      status
    }
    None => status,
  };

  let (mut status, running) = match &stat.container_statuses {
    Some(css) => {
      let mut running = false;
      let status = css
        .iter()
        .rev()
        .find_map(|cs| {
          cs.state.as_ref().and_then(|s| {
            if cs.ready && s.running.is_some() {
              running = true;
            }
            if s
              .waiting
              .as_ref()
              .and_then(|w| w.reason.as_ref().map(|v| !v.is_empty()))
              .unwrap_or_default()
            {
              s.waiting.as_ref().and_then(|w| w.reason.clone())
            } else if s
              .terminated
              .as_ref()
              .and_then(|w| w.reason.as_ref().map(|v| !v.is_empty()))
              .unwrap_or_default()
            {
              s.terminated.as_ref().and_then(|w| w.reason.clone())
            } else if let Some(st) = &s.terminated {
              if st.signal.unwrap_or_default() != 0 {
                Some(format!("Signal:{}", st.signal.unwrap_or_default()))
              } else {
                Some(format!("ExitCode:{}", st.exit_code))
              }
            } else {
              Some(status.clone())
            }
          })
        })
        .unwrap_or_default();
      (status, running)
    }
    None => (status, false),
  };

  if running && status == "Completed" {
    status = "Running".into();
  }

  if pod.metadata.deletion_timestamp.is_none() {
    return status;
  }

  "Terminating".into()
}

fn is_pod_init(sw: Option<ContainerStateWaiting>) -> bool {
  sw.map(|w| w.reason.unwrap_or_default() != "PodInitializing")
    .unwrap_or_default()
}

fn get_container_ports(ports_ref: &Option<Vec<ContainerPort>>) -> Option<String> {
  ports_ref.as_ref().map(|ports| {
    ports
      .iter()
      .map(|c_port| {
        let mut port = String::new();
        if let Some(name) = c_port.name.clone() {
          port = format!("{}:", name);
        }
        port = format!("{}{}", port, c_port.container_port);
        if let Some(protocol) = c_port.protocol.clone() {
          if protocol != "TCP" {
            port = format!("{}/{}", port, c_port.protocol.clone().unwrap());
          }
        }
        port
      })
      .collect::<Vec<_>>()
      .join(", ")
  })
}

#[cfg(test)]
mod tests {
  use super::*;
  use crate::app::test_utils::*;
  use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;

  #[test]
  fn test_get_container_title() {
    let app = App::default();
    assert_eq!(
      get_container_title(&app, 3, "hello"),
      " Pods (ns: all) [0] -> Containers [3] hello"
    );
  }

  #[test]
  fn test_get_container_title_with_resource_breadcrumb() {
    let mut app = App::default();
    app.data.selected.pod_selector_resource = Some("deployment".into());
    assert_eq!(
      get_container_title(&app, 2, ""),
      " Deployment -> Pods (ns: all) [0] -> Containers [2] "
    );
  }

  #[test]
  fn test_get_container_title_with_resource_breadcrumb_and_logs() {
    let mut app = App::default();
    app.data.selected.pod_selector_resource = Some("statefulset".into());
    assert_eq!(
      get_container_title(&app, 1, "-> Logs (nginx) "),
      " Statefulset -> Pods (ns: all) [0] -> Containers [1] -> Logs (nginx) "
    );
  }

  #[test]
  fn test_capitalize_first() {
    assert_eq!(capitalize_first("deployment"), "Deployment");
    assert_eq!(capitalize_first("statefulset"), "Statefulset");
    assert_eq!(capitalize_first(""), "");
    assert_eq!(capitalize_first("A"), "A");
  }

  #[test]
  fn test_pod_from_api() {
    let now = Utc::now();
    let raw = load_resource_from_file::<Pod>("pods");
    let pods_list: Vec<Pod> = raw.items.clone();
    let pods: Vec<KubePod> = raw
      .into_iter()
      .map(|p| KubePod::from_pod_at(p, now))
      .collect();

    assert_eq!(pods.len(), 13);
    assert_eq!(
      pods[0],
      KubePod {
        namespace: "default".into(),
        name: "adservice-f787c8dcd-tb6x2".into(),
        ready: (0, 1),
        status: "Pending".into(),
        restarts: 0,
        cpu: "".into(),
        mem: "".into(),
        node: "".into(),
        ip: "".into(),
        age: utils::to_age(Some(&get_time("2021-04-27T10:13:58Z")), now),
        containers: vec![KubeContainer {
          name: "server".into(),
          image: "gcr.io/google-samples/microservices-demo/adservice:v0.2.2".into(),
          ready: "false".into(),
          status: "<none>".into(),
          restarts: 0,
          liveliness_probe: true,
          readiness_probe: true,
          ports: "9555".into(),
          age: utils::to_age(Some(&get_time("2021-04-27T10:13:58Z")), now),
          pod_name: "adservice-f787c8dcd-tb6x2".into(),
          init: false,
          k8s_obj: None,
        }],
        k8s_obj: pods_list[0].clone()
      }
    );
    assert_eq!(
      pods[1],
      KubePod {
        namespace: "default".into(),
        name: "cartservice-67b89ffc69-s5qp8".into(),
        ready: (0, 1),
        status: "CrashLoopBackOff".into(),
        restarts: 896,
        cpu: "".into(),
        mem: "".into(),
        node: "gke-hello-hipster-default-pool-9e6f6ffb-q16l".into(),
        ip: "10.24.1.9".into(),
        age: utils::to_age(Some(&get_time("2021-04-27T10:13:57Z")), now),
        containers: vec![KubeContainer {
          name: "server".into(),
          image: "gcr.io/google-samples/microservices-demo/cartservice:v0.2.2".into(),
          ready: "false".into(),
          status: "CrashLoopBackOff".into(),
          restarts: 896,
          liveliness_probe: true,
          readiness_probe: true,
          ports: "7070".into(),
          age: utils::to_age(Some(&get_time("2021-04-27T10:13:57Z")), now),
          pod_name: "cartservice-67b89ffc69-s5qp8".into(),
          init: false,
          k8s_obj: None,
        }],
        k8s_obj: pods_list[1].clone()
      }
    );
    assert_eq!(
      pods[3],
      KubePod {
        namespace: "default".into(),
        name: "emailservice-5f8fc7dbb4-5lqdb".into(),
        ready: (1, 1),
        status: "Running".into(),
        restarts: 3,
        cpu: "".into(),
        mem: "".into(),
        node: "gke-hello-hipster-default-pool-9e6f6ffb-xzbc".into(),
        ip: "10.24.0.3".into(),
        age: utils::to_age(Some(&get_time("2021-04-27T10:13:56Z")), now),
        containers: vec![KubeContainer {
          name: "server".into(),
          image: "gcr.io/google-samples/microservices-demo/emailservice:v0.2.2".into(),
          ready: "true".into(),
          status: "Running".into(),
          restarts: 3,
          liveliness_probe: true,
          readiness_probe: true,
          ports: "8080".into(),
          age: utils::to_age(Some(&get_time("2021-04-27T10:13:56Z")), now),
          pod_name: "emailservice-5f8fc7dbb4-5lqdb".into(),
          init: false,
          k8s_obj: None,
        }],
        k8s_obj: pods_list[3].clone()
      }
    );
    let out_of_cpu_pod = &pods[4];
    assert_eq!(out_of_cpu_pod.namespace, "default");
    assert_eq!(out_of_cpu_pod.name, "frontend-5c4745dfdb-6k8wf");
    assert_eq!(out_of_cpu_pod.ready, (0, 0));
    assert_eq!(out_of_cpu_pod.status, "OutOfcpu");
    assert_eq!(out_of_cpu_pod.restarts, 0);
    assert_eq!(out_of_cpu_pod.cpu, "");
    assert_eq!(out_of_cpu_pod.mem, "");
    assert_eq!(
      out_of_cpu_pod.age,
      utils::to_age(Some(&get_time("2021-04-27T10:14:48Z")), now)
    );
    assert_eq!(
      out_of_cpu_pod.containers,
      vec![KubeContainer {
        name: "server".into(),
        image: "gcr.io/google-samples/microservices-demo/frontend:v0.2.2".into(),
        ready: "false".into(),
        status: "<none>".into(),
        restarts: 0,
        liveliness_probe: true,
        readiness_probe: true,
        ports: "8080".into(),
        age: utils::to_age(Some(&get_time("2021-04-27T10:14:48Z")), now),
        pod_name: "frontend-5c4745dfdb-6k8wf".into(),
        k8s_obj: None,
        init: false,
      }]
    );
    assert_eq!(
      out_of_cpu_pod.k8s_obj.metadata.name.as_deref(),
      Some("frontend-5c4745dfdb-6k8wf")
    );
    assert_eq!(
      pods[5],
      KubePod {
        namespace: "default".into(),
        name: "frontend-5c4745dfdb-qz7fg".into(),
        ready: (0, 0),
        status: "Preempting".into(),
        restarts: 0,
        cpu: "".into(),
        mem: "".into(),
        node: "gke-hello-hipster-default-pool-9e6f6ffb-q16l".into(),
        ip: "".into(),
        age: utils::to_age(Some(&get_time("2021-04-27T10:13:57Z")), now),
        containers: vec![KubeContainer {
          name: "server".into(),
          image: "gcr.io/google-samples/microservices-demo/frontend:v0.2.2".into(),
          ready: "false".into(),
          status: "<none>".into(),
          restarts: 0,
          liveliness_probe: false,
          readiness_probe: true,
          ports: "8080/HTTP".into(),
          age: utils::to_age(Some(&get_time("2021-04-27T10:13:57Z")), now),
          pod_name: "frontend-5c4745dfdb-qz7fg".into(),
          k8s_obj: None,
          init: false,
        }],
        k8s_obj: pods_list[5].clone()
      }
    );
    assert_eq!(
      pods[6],
      KubePod {
        namespace: "default".into(),
        name: "frontend-5c4745dfdb-6k8wf".into(),
        ready: (0, 0),
        status: "Failed".into(),
        restarts: 0,
        cpu: "".into(),
        mem: "".into(),
        node: "gke-hello-hipster-default-pool-9e6f6ffb-q16l".into(),
        ip: "".into(),
        age: utils::to_age(Some(&get_time("2021-04-27T10:14:48Z")), now),
        containers: vec![KubeContainer {
          name: "server".into(),
          image: "gcr.io/google-samples/microservices-demo/frontend:v0.2.2".into(),
          ready: "false".into(),
          status: "<none>".into(),
          restarts: 0,
          liveliness_probe: true,
          readiness_probe: true,
          ports: "8080, 8081/UDP, Foo:8082/UDP, 8083".into(),
          age: utils::to_age(Some(&get_time("2021-04-27T10:14:48Z")), now),
          pod_name: "frontend-5c4745dfdb-6k8wf".into(),
          k8s_obj: None,
          init: false,
        }],
        k8s_obj: pods_list[6].clone()
      }
    );
    assert_eq!(
      pods[11],
      KubePod {
        namespace: "default".into(),
        name: "pod-init-container".into(),
        ready: (0, 1),
        status: "Init:1/2".into(),
        restarts: 0,
        cpu: "".into(),
        mem: "".into(),
        node: "k3d-my-kdash-cluster-server-0".into(),
        ip: "10.42.0.20".into(),
        age: utils::to_age(Some(&get_time("2021-06-18T08:57:56Z")), now),
        containers: vec![
          KubeContainer {
            name: "main-busybox".into(),
            image: "busybox".into(),
            ready: "false".into(),
            status: "PodInitializing".into(),
            restarts: 0,
            liveliness_probe: false,
            readiness_probe: false,
            ports: "".into(),
            age: utils::to_age(Some(&get_time("2021-06-18T08:57:56Z")), now),
            pod_name: "pod-init-container".into(),
            k8s_obj: None,
            init: false,
          },
          KubeContainer {
            name: "init-busybox1".into(),
            image: "busybox".into(),
            ready: "true".into(),
            status: "Completed".into(),
            restarts: 0,
            liveliness_probe: false,
            readiness_probe: false,
            ports: "".into(),
            age: utils::to_age(Some(&get_time("2021-06-18T08:57:56Z")), now),
            pod_name: "pod-init-container".into(),
            k8s_obj: None,
            init: true,
          },
          KubeContainer {
            name: "init-busybox2".into(),
            image: "busybox".into(),
            ready: "false".into(),
            status: "Running".into(),
            restarts: 0,
            liveliness_probe: false,
            readiness_probe: false,
            ports: "".into(),
            age: utils::to_age(Some(&get_time("2021-06-18T08:57:56Z")), now),
            pod_name: "pod-init-container".into(),
            k8s_obj: None,
            init: true,
          }
        ],
        k8s_obj: pods_list[11].clone()
      }
    );
    assert_eq!(
      pods[12],
      KubePod {
        namespace: "default".into(),
        name: "pod-init-container-2".into(),
        ready: (0, 1),
        status: "Completed".into(),
        restarts: 0,
        cpu: "".into(),
        mem: "".into(),
        node: "k3d-my-kdash-cluster-server-0".into(),
        ip: "10.42.0.21".into(),
        age: utils::to_age(Some(&get_time("2021-06-18T09:26:11Z")), now),
        containers: vec![
          KubeContainer {
            name: "main-busybox".into(),
            image: "busybox".into(),
            ready: "false".into(),
            status: "Completed".into(),
            restarts: 0,
            liveliness_probe: false,
            readiness_probe: false,
            ports: "".into(),
            age: utils::to_age(Some(&get_time("2021-06-18T09:26:11Z")), now),
            pod_name: "pod-init-container-2".into(),
            k8s_obj: None,
            init: false,
          },
          KubeContainer {
            name: "init-busybox1".into(),
            image: "busybox".into(),
            ready: "true".into(),
            status: "Completed".into(),
            restarts: 0,
            liveliness_probe: false,
            readiness_probe: false,
            ports: "".into(),
            age: utils::to_age(Some(&get_time("2021-06-18T09:26:11Z")), now),
            pod_name: "pod-init-container-2".into(),
            k8s_obj: None,
            init: true,
          },
          KubeContainer {
            name: "init-busybox2".into(),
            image: "busybox".into(),
            ready: "true".into(),
            status: "Completed".into(),
            restarts: 0,
            liveliness_probe: false,
            readiness_probe: false,
            ports: "".into(),
            age: utils::to_age(Some(&get_time("2021-06-18T09:26:11Z")), now),
            pod_name: "pod-init-container-2".into(),
            k8s_obj: None,
            init: true,
          }
        ],
        k8s_obj: pods_list[12].clone()
      }
    );
    // TODO add tests for NodeLost case
  }

  #[test]
  fn test_pending_pod_ready_count_uses_declared_containers() {
    let pod = Pod {
      metadata: ObjectMeta {
        name: Some("pending-pod".into()),
        namespace: Some("default".into()),
        ..Default::default()
      },
      spec: Some(PodSpec {
        containers: vec![Container {
          name: "server".into(),
          image: Some("busybox".into()),
          ..Default::default()
        }],
        ..Default::default()
      }),
      status: Some(PodStatus {
        phase: Some("Pending".into()),
        container_statuses: None,
        ..Default::default()
      }),
    };

    let pod = KubePod::from(pod);

    assert_eq!(pod.ready, (0, 1));
    assert_eq!(pod.status, "Pending");
  }
}
