use crate::bus::{publish_system_event, publish_watcher_event, subscribe_system_event, SystemEvent};
use shared::core::{self, ChainData};
use shared::core::print_error_chain;
use crate::executor::ExecutorCommand;
use crate::log;
use crate::protocol::ProtocolMessage;
use crate::protocol::{Connector, ConnectorCommand};
use crate::seeds_constants::SEED_NODES;
use crate::watcher::{WatcherCommand, WatcherMode};
use super::{ Blockchain, BlockchainError, ValidationError };

use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;

use thiserror::*;
use tokio::select;
use tokio::sync::mpsc;
use uuid::Uuid;
use vlogger::*;

#[derive(Debug, Clone)]
pub struct TcpPeer {
  pub id: Uuid,
  pub addr: SocketAddr,
  pub sender: tokio::sync::mpsc::Sender<ProtocolMessage>,
}

impl TcpPeer {
  pub fn new(
    id: Uuid,
    addr: SocketAddr,
    sender: tokio::sync::mpsc::Sender<ProtocolMessage>,
  ) -> Self {
    Self { id, addr, sender }
  }
}

#[allow(dead_code)]
pub struct Node {
  pub tcp_connector: Option<mpsc::Sender<ConnectorCommand>>,
  pub id: Uuid,
  pub addr: Option<SocketAddr>,
  pub tcp_peers: HashMap<Uuid, TcpPeer>,
  chain: Blockchain,
  listner_handle: Option<tokio::task::JoinHandle<()>>,
  exec_tx: mpsc::Sender<ExecutorCommand>,
  rx: mpsc::Receiver<NodeCommand>,
  tx: mpsc::Sender<NodeCommand>,
}

#[derive(Debug, Error)]
pub enum NodeError {
  #[error("Block chain error")]
  ChainError(#[from] BlockchainError),
}

#[derive(Debug, Clone)]
pub enum NodeCommand {
  AddPeer(TcpPeer),
  RemovePeer {
    peer_id: Uuid,
  },
  ProcessMessage {
    peer_id: Uuid,
    message: ProtocolMessage,
  },
  ProcessChainData(ChainData),
  StartListner(SocketAddr),
  PingAddr(String),
  PingId(String),
  CreateBlock,
  DisplayBlockInteractive,
  DisplayBlockByKey(String),
  DisplayBlockByHeight(u64),
  ListBlocks,
  ListPeers,
  ShowId,
  AwardCurrency { address: String, amount: u64 },
  ConnectToSeeds,
  ConnectTcpPeer(String),
  BootStrap,
  Exit,
}

impl Node {
  pub fn peer_addresses(&self) -> Vec<SocketAddr> {
    let mut addr: Vec<SocketAddr> = self
      .tcp_peers
      .iter()
      .map(|p| p.1.addr.to_string().parse::<SocketAddr>().unwrap())
      .collect();
    if let Some(a) = self.addr {
      addr.push(a.clone());
    }
    addr
  }

  pub fn list_peers(&self) -> String {
    let mut ret = String::from("Peer List\n-----------\n");
    for (i, p) in self.tcp_peers.iter().enumerate() {
      ret.push_str(format!("Peer #{i}: {}\n", p.1.id).as_str())
    }
    ret
  }

  pub async fn show_id(&self) {
    log(msg!(DEBUG, "Node Id: {}", self.id))
  }

  async fn remove_tcp_peer(&mut self, peer_id: Uuid) {
    log(msg!(DEBUG, "Removing Peer {peer_id}"));
    self.tcp_peers.remove_entry(&peer_id);
  }

  async fn add_tcp_peer(&mut self, peer: TcpPeer) {
    log(msg!(DEBUG, "Added Peer from address: {}", peer.addr));
    self.tcp_peers.insert(peer.id, peer);
  }

  pub async fn new_with_id(
    id: uuid::Uuid,
    exec_tx: mpsc::Sender<ExecutorCommand>,
    addr: Option<SocketAddr>,
    chain: Blockchain,
  ) -> Self {
    let (tx, rx) = mpsc::channel::<NodeCommand>(100);
    Self {
      id,
      tcp_peers: HashMap::new(),
      addr,
      exec_tx,
      chain,
      listner_handle: None,
      tcp_connector: None,
      tx,
      rx,
    }
  }

  pub fn new(
    addr: Option<SocketAddr>,
    exec_tx: mpsc::Sender<ExecutorCommand>,
    chain: Blockchain,
  ) -> Self {
    let (tx, rx) = mpsc::channel::<NodeCommand>(100);
    Self {
      id: Uuid::new_v4(),
      tcp_peers: HashMap::new(),
      addr,
      exec_tx,
      listner_handle: None,
      tcp_connector: None,
      chain,
      tx,
      rx,
    }
  }

  async fn shutdown(&mut self) {
    if let Some(conn) = &self.tcp_connector {
      let res = conn.send(ConnectorCommand::Shutdown).await;
      if res.is_err() {
        log(msg!(ERROR, "Failed to send shutdown signal to connector"));
      }
    }
    let _ = self.chain.shutdown().await;
  }

  fn get_blocks(&self) -> Result<Vec<Arc<core::Block>>, NodeError> {
    Ok(self.chain.blocks()?)
  }

  pub async fn process_message(&mut self, peer_id: uuid::Uuid, message: ProtocolMessage) {
    match message {
      ProtocolMessage::BootstrapRequest { .. } => {
        log(msg!(DEBUG, "Received BootstrapRequest from {peer_id}"));
        let peer = &self.tcp_peers[&peer_id];
        let resp = ProtocolMessage::BootstrapResponse {
          blocks: {
            if let Ok(blocks) = self.get_blocks() {
              serde_json::to_string(
                &blocks
                  .iter()
                  .map(|f| (**f).clone())
                  .collect::<Vec<core::Block>>(),
              )
              .map_err(|e| {
                log(msg!(
                  ERROR,
                  "Failed to serde Chain for BootstrapResponse: {e}"
                ));
                e
              })
              .ok()
            } else {
              None
            }
          },
        };
        peer.sender.send(resp).await.unwrap();
        log(msg!(DEBUG, "Send BootstrapResponse to {peer_id}"));
      }
      ProtocolMessage::BootstrapResponse { blocks } => {
        log(msg!(DEBUG, "Received BootstrapResponse from seed"));
        self.chain = Blockchain::build(blocks).unwrap();
      }
      ProtocolMessage::Pong { peer_id } => {
        log(msg!(DEBUG, "Received Pong from {peer_id}"));
      }
      ProtocolMessage::Ping { peer_id } => {
        log(msg!(DEBUG, "Received Ping from {peer_id}"));
        let resp = ProtocolMessage::Pong {
          peer_id: self.id.clone(),
        };
        let peer = &self.tcp_peers[&peer_id];
        peer.sender.send(resp).await.unwrap();
      }
      ProtocolMessage::GetPeersRequest { peer_id } => {
        log(msg!(DEBUG, "Received GetPeersRequest from {peer_id}"));
        let peers = self.peer_addresses();
        let resp = ProtocolMessage::GetPeersResponse {
          peer_addresses: peers,
        };
        let peer = &self.tcp_peers[&peer_id];
        peer.sender.send(resp).await.unwrap();
      }
      ProtocolMessage::Block { block, .. } => {
        log(msg!(DEBUG, "Received Block from {peer_id}"));
        if let Err(_e) = self.chain.add_block(block.into()) {
          log(msg!(
            DEBUG,
            "TODO: implement error handling in {}:{}",
            file!(),
            line!()
          ));
        }
      }
      ProtocolMessage::ChainData { data, .. } => {
        log(msg!(DEBUG, "Received ChainData from {peer_id}"));
        self.chain.apply_chain_data(data).unwrap()
      }
      _ => {
        log(msg!(DEBUG, "TODO: implement this message type"));
      }
    }
  }

  pub async fn send_message_to_peer_addr(&self, addr: SocketAddr, msg: ProtocolMessage) {
    if let Some((_, peer)) = self.tcp_peers.iter().find(|(_, v)| v.addr == addr) {
      if let Err(e) = peer.sender.send(msg).await {
        log(msg!(ERROR, "Error Sending message to peer: {e}"));
      }
      log(msg!(DEBUG, "Sent BootstrapRequest to seed"));
    } else {
      log(msg!(
        ERROR,
        "Error Sending message to peer: peer not in list"
      ));
    }
  }

  pub async fn send_message_to_peer_id(&self, id: Uuid, msg: ProtocolMessage) {
    if let Some(peer) = self.tcp_peers.get(&id) {
      if let Err(e) = peer.sender.send(msg).await {
        log(msg!(ERROR, "Error Sending message to peer: {e}"));
      }
    }
  }

  async fn send_message_to_seed(&self, msg: ProtocolMessage) {
    for seed in SEED_NODES.iter() {
      if let Some(_) = self.tcp_peers.iter().find(|(_, v)| v.addr == *seed) {
        self.send_message_to_peer_addr(*seed, msg).await;
        return;
      } else {
        self.send_message_to_peer_addr(*seed, msg).await;
        return;
      }
    }
    log(msg!(ERROR, "No Seed Nodes Avaliable"));
  }

  async fn bootstrap(&mut self) -> Result<(), ValidationError> {
    log(msg!(DEBUG, "Bootstrapping"));

    let message = ProtocolMessage::BootstrapRequest {
      peer_id: self.id,
      version: "".to_string(),
    };
    self.send_message_to_seed(message).await;

    Ok(())
  }

  async fn broadcast_network_data(&self, data: ChainData) {
    for (id, peer) in &self.tcp_peers {
      let message = ProtocolMessage::ChainData {
        peer_id: self.id,
        data: data.clone(),
      };
      peer.sender.send(message).await.unwrap();
      log(msg!(DEBUG, "Send Transaction message to {id}"));
    }
  }

  async fn broadcast_block(&self, block: &core::Block) {
    for (id, peer) in &self.tcp_peers {
      let message = ProtocolMessage::Block {
        peer_id: self.id,
        height: block.head().height as u64,
        block: block.clone(),
      };
      peer.sender.send(message).await.unwrap();
      log(msg!(DEBUG, "Send Block message to {id}"));
    }
  }

  pub fn tx(&self) -> mpsc::Sender<NodeCommand> {
    return self.tx.clone();
  }

  pub fn exec_tx(&self) -> mpsc::Sender<ExecutorCommand> {
    return self.exec_tx.clone();
  }

  async fn connector_cmd(&self, cmd: ConnectorCommand) {
    match &self.tcp_connector {
      Some(t) => match t.send(cmd).await {
        Ok(()) => {}
        Err(e) => log(msg!(ERROR, "Failed to Send Command to connector: {}", e)),
      },
      None => log(msg!(ERROR, "No Connector Availiable")),
    }
  }

  async fn start_connection_listner(&mut self, addr: SocketAddr) {
    log(msg!(DEBUG, "Starting Connection Listener"));
    let (con_tx, con_rx) = mpsc::channel::<ConnectorCommand>(100);

    self.tcp_connector = Some(con_tx);

    self.listner_handle = Some(tokio::spawn({
      let mut connector = Connector::new(self.id, addr, self.exec_tx(), con_rx);
      log(msg!(DEBUG, "Connector Build"));
      async move { connector.start().await }
    }));
  }

  async fn connect_to_seed(&mut self) {
    self
      .connector_cmd(ConnectorCommand::ConnectToTcpSeed(SEED_NODES[0]))
      .await;
  }

  async fn accept_command(&mut self) {
    while let Some(command) = self.rx.recv().await {
      match command {
        NodeCommand::BootStrap => {
          log(msg!(DEBUG, "Received NodeCommand::BootStrap"));
          let _ = self.bootstrap().await;
        }
        NodeCommand::StartListner(addr) => {
          self.start_connection_listner(addr).await;
        }
        NodeCommand::ConnectToSeeds => {
          self.connect_to_seed().await;
        }
        NodeCommand::ConnectTcpPeer(addr) => {
          log(msg!(DEBUG, "Received ConnectToPeer: {addr}"));
          if let Ok(addr_sock) = addr.parse::<SocketAddr>() {
            let mes = ConnectorCommand::ConnectToTcpPeer(addr_sock);
            self.connector_cmd(mes).await;
          } else {
            log(msg!(ERROR, "Failed to Parse to sock_addr: {addr}"));
          }
        }
        NodeCommand::PingAddr(addr) => {
          if let Ok(addr_sock) = addr.parse::<SocketAddr>() {
            let mes = ProtocolMessage::Ping { peer_id: self.id };
            self.send_message_to_peer_addr(addr_sock, mes).await;
          } else {
            log(msg!(ERROR, "Failed to Parse to sock_addr: {addr}"));
          }
        }
        NodeCommand::PingId(id) => {
          if let Ok(id) = id.parse::<Uuid>() {
            let mes = ProtocolMessage::Ping { peer_id: self.id };
            self.send_message_to_peer_id(id, mes).await;
          } else {
            log(msg!(ERROR, "Failed to Parse to sock_addr: {id}"));
          }
        }
        NodeCommand::AddPeer(peer) => {
          self.add_tcp_peer(peer).await;
        }
        NodeCommand::RemovePeer { peer_id } => {
          self.remove_tcp_peer(peer_id).await;
        }
        NodeCommand::ProcessMessage { peer_id, message } => {
          self.process_message(peer_id, message).await;
        }
        NodeCommand::AwardCurrency { address, amount } => {
          if let Err(e) = self.chain.award_currency(address, amount) {
            print_error_chain(&e.into());
          }
        }
        NodeCommand::ProcessChainData(data) => {
          if let Err(e) = self.chain.apply_chain_data(data.clone()) {
            print_error_chain(&e.into());
          }
          self.broadcast_network_data(data).await;
        }
        NodeCommand::CreateBlock => {
          log(msg!(DEBUG, "Received CreateBlock Command"));
          if let Ok(block) = self.chain.create_block() {
            log(msg!(
              INFO,
              "Created Block with hash {}",
              block.head().block_hash()
            ));
            self.broadcast_block(&block).await;
          }
        }
        NodeCommand::DisplayBlockInteractive => {
          let blocks = match self.chain.list_blocks() {
            Ok(b) => b,
            Err(e) => return print_error_chain(&e.into()),
          };
          let wat_cmd = WatcherCommand::SetMode(WatcherMode::Select {
            content: blocks.into(),
            title: "Select Block to display".to_string(),
            callback: Box::new(ExecutorCommand::Node(NodeCommand::DisplayBlockByKey("".to_string()))),
            index: 0
          });
          publish_watcher_event(wat_cmd);
        }
        NodeCommand::DisplayBlockByKey(key) => self.chain.display_block_by_key(key),
        NodeCommand::DisplayBlockByHeight(height) => self.chain.display_block_by_height(height),
        NodeCommand::ListBlocks => {
          log(msg!(DEBUG, "Received DebugListBlocks command"));
          match self.chain.list_blocks() {
            Ok(s) => log(s.join("\n")),
            Err(e) => print_error_chain(&e.into()),
          }
        }
        NodeCommand::ListPeers => {
          log(msg!(DEBUG, "Received DebugListPeers command"));
          log(self.list_peers());
        }
        NodeCommand::ShowId => {
          log(msg!(DEBUG, "Received DebugListBlocks command"));
          self.show_id().await;
        }
        NodeCommand::Exit => {
          log(msg!(DEBUG, "Node Exit"));
          break;
        }
      }
    }
  }

  pub async fn run(&mut self) {
    if let Some(addr) = self.addr {
      self.start_connection_listner(addr).await;
    } else {
      self
        .start_connection_listner(SocketAddr::new(
          std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)),
          8080,
        ))
        .await;
    };
    let mut system_rx = subscribe_system_event();
    publish_system_event(SystemEvent::NodeStarted);

    loop {
      select! {
        _ = self.accept_command() => {

        }
        event_result = system_rx.recv() => {
          match event_result {
            Ok(e) => {
              match e {
                SystemEvent::Shutdown => {
                  break;
                }
                _ => {}
              }
            }
            _ => {}
          }
        }
      }
    }
    self.shutdown().await;
  }
}
