use anyhow::Context;
use std::net::SocketAddr;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net;
use tokio::sync::mpsc;
use vlogger::*;
use shared::core::print_error_chain;
use thiserror::*;

use crate::log;
use super::Connection;
use crate::bus::*;
use crate::executor::ExecutorCommand;
use crate::node::node;
use crate::node::{NetworkError, error};
use crate::protocol::ProtocolMessage;

pub enum ConnectorCommand {
  ConnectToTcpPeer(SocketAddr),
  ConnectToTcpSeed(SocketAddr),
  Shutdown,
}

pub struct Connector {
  node_id: uuid::Uuid,
  addr: SocketAddr,
  exec_tx: mpsc::Sender<ExecutorCommand>,
  rx: mpsc::Receiver<ConnectorCommand>,
  exit: bool,
}

#[derive(Error, Debug)]
pub enum ConnectorError {
  #[error("Connection failed")]
  ConnectionError(#[from] anyhow::Error),
}

const MAX_LISTNER_TRIES: usize = 5;

impl Connector {
  pub fn new(
    node_id: uuid::Uuid,
    addr: SocketAddr,
    exec_tx: mpsc::Sender<ExecutorCommand>,
    rx: mpsc::Receiver<ConnectorCommand>,
  ) -> Self {
    Self {
      node_id,
      addr,
      exec_tx,
      rx,
      exit: false,
    }
  }

  pub async fn start(&mut self) {
    let mut listner: Option<tokio::net::TcpListener> = None;
    let mut listner_err = None;
    for _ in 0..MAX_LISTNER_TRIES {
      match tokio::net::TcpListener::bind(self.addr).await {
        Ok(l) => {
          log(msg!(DEBUG, "Listening on address: {}", self.addr));
          listner = Some(l);
          break;
        }
        Err(e) => {
          self.addr.set_port(self.addr.port() + 1);
          listner_err = Some(e);
        }
      };
    }
    if let Some(listener) = listner {
      while !self.exit {
        tokio::select! {
          cmd_result = self.rx.recv() => {
            match cmd_result {
              Some(cmd) => {
                self.execute_cmd(cmd).await;
              }
              None => {
                log(msg!(DEBUG, "Command channel closed"));
                break;
              }
            }
          }
          accept_result = listener.accept() => {
            match accept_result {
              Ok((stream, addr)) => {
                log(msg!(DEBUG, "Accepted connection from {}", addr));
                self.establish_connection_inbound(stream, addr).await;
              }
              Err(e) => {
                log(msg!(ERROR, "Failed to accept connection: {}", e));
              }
            }
          }
        }
      }
    } else {
      log(msg!(
        FATAL,
        "Failed to start TCP Listener: {}",
        listner_err.unwrap()
      ));
    }
  }

  async fn execute_cmd(&mut self, cmd: ConnectorCommand) {
    match cmd {
      ConnectorCommand::ConnectToTcpPeer(addr) => self.connect_to_peer(addr).await,
      ConnectorCommand::ConnectToTcpSeed(addr) => {
        self.connect_to_seed(addr).await;
      }
      ConnectorCommand::Shutdown => {
        self.exit = true;
      }
    }
  }

  pub async fn connect_to_seed(&self, addr: SocketAddr) {
    match net::TcpStream::connect(addr)
      .await
      .with_context(|| format!("Connecting to {}", addr))
    {
      Ok(stream) => self.establish_connection_to_seed(stream, addr).await,
      Err(e) => {
        // let err = ConnectorError::ConnectionError(e.into());
        print_error_chain(&e.into());
      }
    }
  }

  pub async fn connect_to_peer(&self, addr: SocketAddr) {
    match net::TcpStream::connect(addr).await {
      Ok(stream) => self.establish_connection_outbound(stream, addr).await,
      Err(e) => {
        let err = ConnectorError::ConnectionError(e.into());
        print_error_chain(&err.into());
      }
    }
  }

  pub async fn establish_connection_to_seed(
    &self,
    mut stream: tokio::net::TcpStream,
    addr: SocketAddr,
  ) {
    let handshake = ProtocolMessage::Handshake {
      peer_id: self.node_id,
      version: "".to_string(),
    };
    match Connector::send_message(&mut stream, &handshake).await {
      Ok(()) => {
        if let Ok(mes) = Connector::receive_message(&mut stream).await {
          let (ch_tx, ch_rx) = mpsc::channel::<ProtocolMessage>(100);
          let peer = match mes {
            ProtocolMessage::HandshakeAck { peer_id, .. } => {
              node::TcpPeer::new(peer_id, addr, ch_tx)
            }
            _ => {
              log(msg!(
                ERROR,
                "Invalid Message On Connetion Establishment: {mes}"
              ));
              return;
            }
          };
          let cmd = ExecutorCommand::Node(node::NodeCommand::AddPeer(peer.clone()));
          publish_network_event(NetworkEvent::SeedConnected(addr.to_string()));
          let _ = self.exec_tx.send(cmd).await;
          Connection::new(self.node_id, peer.id, stream, self.exec_tx.clone(), ch_rx)
            .start()
            .await;
        }
      }
      Err(e) => print_error_chain(&e.into()),
    }
  }

  async fn establish_connection_outbound(
    &self,
    mut stream: tokio::net::TcpStream,
    addr: SocketAddr,
  ) {
    let handshake = ProtocolMessage::Handshake {
      peer_id: self.node_id,
      version: "".to_string(),
    };
    match Connector::send_message(&mut stream, &handshake).await {
      Ok(()) => {
        if let Ok(mes) = Connector::receive_message(&mut stream).await {
          let (ch_tx, ch_rx) = mpsc::channel::<ProtocolMessage>(100);
          let peer = match mes {
            ProtocolMessage::HandshakeAck { peer_id, .. } => {
              node::TcpPeer::new(peer_id, addr, ch_tx)
            }
            _ => {
              log(msg!(
                ERROR,
                "Invalid Message On Connetion Establishment: {mes}"
              ));
              return;
            }
          };
          let cmd = ExecutorCommand::Node(node::NodeCommand::AddPeer(peer.clone()));
          let _ = self.exec_tx.send(cmd).await;
          Connection::new(self.node_id, peer.id, stream, self.exec_tx.clone(), ch_rx)
            .start()
            .await;
        }
      }
      Err(e) => print_error_chain(&e.into()),
    }
  }

  async fn establish_connection_inbound(
    &self,
    mut stream: tokio::net::TcpStream,
    addr: SocketAddr,
  ) {
    if let Ok(mes) = Connector::receive_message(&mut stream).await {
      let (ch_tx, ch_rx) = mpsc::channel::<ProtocolMessage>(100);
      let peer = match mes {
        ProtocolMessage::Handshake { peer_id, .. } => {
          let ack = ProtocolMessage::HandshakeAck {
            peer_id: self.node_id,
            version: "".to_string(),
          };
          match Connector::send_message(&mut stream, &ack).await {
            Ok(()) => node::TcpPeer::new(peer_id, addr, ch_tx),
            Err(e) => return print_error_chain(&e.into()),
          }
        }
        _ => {
          log(msg!(
            ERROR,
            "Invalid Message On Connetion Establishment: {mes}"
          ));
          return;
        }
      };
      let cmd = ExecutorCommand::Node(node::NodeCommand::AddPeer(peer.clone()));
      let _ = self.exec_tx.send(cmd).await;
      Connection::new(self.node_id, peer.id, stream, self.exec_tx.clone(), ch_rx)
        .start()
        .await;
    }
  }

  pub async fn send_message(
    stream: &mut net::TcpStream,
    message: &ProtocolMessage,
  ) -> Result<(), NetworkError> {
    let json = serde_json::to_string(message).map_err(|_e| NetworkError::TODO)?;
    let data = json.as_bytes();

    let len = data.len() as u32;
    stream
      .write_all(&len.to_be_bytes())
      .await
      .map_err(|_e| NetworkError::TODO)?;

    stream
      .write_all(data)
      .await
      .map_err(|_e| NetworkError::TODO)?;
    stream.flush().await.map_err(|_e| NetworkError::TODO)?;
    Ok(())
  }

  pub async fn receive_message(
    stream: &mut tokio::net::TcpStream,
  ) -> Result<ProtocolMessage, error::NetworkError> {
    let mut len_bytes = [0u8; 4];
    stream
      .read_exact(&mut len_bytes)
      .await
      .map_err(|_e| NetworkError::TODO)?;

    let len = u32::from_be_bytes(len_bytes) as usize;

    if len >= super::message::MAX_MESSAGE_SIZE {
      return Err(NetworkError::TODO);
    }

    let mut data = vec![0u8; len];
    stream
      .read_exact(&mut data)
      .await
      .map_err(|_e| NetworkError::TODO)?;

    let json = String::from_utf8(data).map_err(|_e| NetworkError::TODO)?;

    let message: ProtocolMessage = serde_json::from_str(&json).map_err(|_e| NetworkError::TODO)?;

    Ok(message)
  }
}
