use std::net::SocketAddr;

use tokio::sync::mpsc;
use vlogger::*;

use crate::bus::{NetworkEvent, SystemEvent, subscribe_system_event};
use crate::executor::{Executor, ExecutorCommand};
use crate::{log, node};
use crate::node::{Node, NodeCommand};
use cli_renderer::{RenderLayoutKind, Renderer};
use crate::watcher::Watcher;

#[derive(Default)]
pub struct WatcherBuilder {
  addr: Option<SocketAddr>,
  database: Option<String>,
  bootstrap: bool,
  debug: bool,
  seed: bool,
  render: bool,
}

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

  pub fn addr(mut self, addr: Option<SocketAddr>) -> Self {
    self.addr = addr;
    self
  }

  pub fn database(mut self, database: Option<String>) -> Self {
    self.database = database;
    self
  }

  pub fn debug(mut self, debug: bool) -> Self {
    self.debug = debug;
    self
  }

  pub fn bootstrap(mut self, bootstrap: bool) -> Self {
    self.bootstrap = bootstrap;
    self
  }

  pub fn render(mut self, render: bool) -> Self {
    self.render = render;
    self
  }

  pub fn seed(mut self, seed: bool) -> Self {
    self.seed = seed;
    self
  }

  pub async fn start(mut self) -> Watcher {
    let (exec_tx, exec_rx) = mpsc::channel::<ExecutorCommand>(100);
    let mut sys_event = subscribe_system_event();

    if self.debug {
      Watcher::log_memory().await;
    }

    let renderer = Renderer::new(RenderLayoutKind::CliHorizontal);

    log(msg!(DEBUG, "Database Location: {:?}", self.database));
    if self.seed {
      self.addr = Some(crate::seeds_constants::SEED_NODES[0]);
    }

    let chain = node::Blockchain::build(None).unwrap();
    let mut node = Node::new(self.addr.clone(), exec_tx.clone(), chain);
    log(msg!(INFO, "Built Node"));

    let executor_handle = tokio::spawn({
      let node_tx = node.tx();
      async move {
        let _ = Executor::new(node_tx, exec_rx).run().await;
      }
    });

    for i in 0..3 {
      if let Ok(ev) = sys_event.recv().await {
        match ev {
          SystemEvent::ExecutorStarted => {
            log(msg!(INFO, "Executor Started"));
            break;
          }
          _ => log(msg!(WARNING, "Wrong Event: {ev:?}! Retrying... (try {i})")),
        }
      }
    }

    let node_tx = node.tx();
    let node_handle = tokio::spawn({
      async move {
        node.run().await;
      }
    });

    for i in 0..3 {
      if let Ok(ev) = sys_event.recv().await {
        match ev {
          SystemEvent::NodeStarted => {
            log(msg!(INFO, "Executor Started"));
            break;
          }
          _ => log(msg!(WARNING, "Wrong Event: {ev:?}! Retrying... (try {i})")),
        }
      }
    }

    if self.bootstrap {
      let exec_tx = exec_tx.clone();

      tokio::spawn(async move {
        let seed_cmd = ExecutorCommand::Node(NodeCommand::ConnectToSeeds);
        let mut ev_rx = crate::bus::subscribe_network_event();
        let _ = exec_tx.send(seed_cmd).await;

        while let Ok(e) = ev_rx.recv().await {
          match e {
            NetworkEvent::SeedConnected(_) => {
              let bootstrap_cmd = ExecutorCommand::Node(NodeCommand::BootStrap);
              let _ = exec_tx.send(bootstrap_cmd).await;
            }
            _ => {}
          }
        }
      });
    }

    let cmd_history = Vec::new();
    let history_index = 0;
    let cmd_buffer = String::new();
    let handles = vec![executor_handle, node_handle];
    Watcher::new(
      node_tx,
      exec_tx,
      cmd_buffer,
      cmd_history,
      history_index,
      handles,
      renderer,
    )
  }
}
