use std::{io::Error as IoError, iter, net::ToSocketAddrs as _};
use derive_more::{AsRef, Display};
use log::info;
use network_clique::{Dialer, Listener, PeerId, PublicKey, SecretKey};
use parity_scale_codec::{Decode, Encode};
use sp_core::crypto::KeyTypeId;
use tokio::net::{TcpListener, TcpStream, ToSocketAddrs};
use crate::{
    phron_primitives::AuthorityId,
    crypto::{verify, AuthorityPen, Signature},
    network::{AddressingInformation, NetworkIdentity},
};
const LOG_TARGET: &str = "tcp-network";
pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"a0vn");
#[derive(PartialEq, Eq, Clone, Debug, Display, Hash, Decode, Encode, AsRef)]
#[as_ref(forward)]
pub struct AuthorityIdWrapper(AuthorityId);
impl From<AuthorityId> for AuthorityIdWrapper {
    fn from(value: AuthorityId) -> Self {
        AuthorityIdWrapper(value)
    }
}
impl PeerId for AuthorityIdWrapper {}
impl PublicKey for AuthorityIdWrapper {
    type Signature = Signature;
    fn verify(&self, message: &[u8], signature: &Self::Signature) -> bool {
        verify(&self.0, message, signature)
    }
}
#[async_trait::async_trait]
impl SecretKey for AuthorityPen {
    type Signature = Signature;
    type PublicKey = AuthorityIdWrapper;
    fn sign(&self, message: &[u8]) -> Self::Signature {
        AuthorityPen::sign(self, message)
    }
    fn public_key(&self) -> Self::PublicKey {
        self.authority_id().into()
    }
}
#[derive(Debug, Hash, Clone, PartialEq, Eq)]
pub enum AddressingInformationError {
    NoAddress,
}
#[derive(Debug, Hash, Encode, Decode, Clone, PartialEq, Eq)]
struct TcpAddressingInformation {
    peer_id: AuthorityId,
    primary_address: String,
    other_addresses: Vec<String>,
}
impl TcpAddressingInformation {
    fn new(
        addresses: Vec<String>,
        peer_id: AuthorityId,
    ) -> Result<TcpAddressingInformation, AddressingInformationError> {
        let mut addresses = addresses.into_iter();
        let primary_address = match addresses.next() {
            Some(address) => address,
            None => return Err(AddressingInformationError::NoAddress),
        };
        Ok(TcpAddressingInformation {
            primary_address,
            other_addresses: addresses.collect(),
            peer_id,
        })
    }
    fn peer_id(&self) -> AuthorityId {
        self.peer_id.clone()
    }
}
#[derive(Debug, Hash, Encode, Decode, Clone, PartialEq, Eq)]
pub struct SignedTcpAddressingInformation {
    addressing_information: TcpAddressingInformation,
    signature: Signature,
}
impl AddressingInformation for SignedTcpAddressingInformation {
    type PeerId = AuthorityIdWrapper;
    fn peer_id(&self) -> Self::PeerId {
        self.addressing_information.peer_id().into()
    }
    fn verify(&self) -> bool {
        self.peer_id()
            .verify(&self.addressing_information.encode(), &self.signature)
    }
    fn address(&self) -> String {
        self.addressing_information.primary_address.clone()
    }
}
impl NetworkIdentity for SignedTcpAddressingInformation {
    type PeerId = AuthorityIdWrapper;
    type AddressingInformation = SignedTcpAddressingInformation;
    fn identity(&self) -> Self::AddressingInformation {
        self.clone()
    }
}
impl SignedTcpAddressingInformation {
    fn new(
        addresses: Vec<String>,
        authority_pen: &AuthorityPen,
    ) -> Result<SignedTcpAddressingInformation, AddressingInformationError> {
        let peer_id = authority_pen.authority_id();
        let addressing_information = TcpAddressingInformation::new(addresses, peer_id)?;
        let signature = authority_pen.sign(&addressing_information.encode());
        Ok(SignedTcpAddressingInformation {
            addressing_information,
            signature,
        })
    }
}
#[derive(Clone)]
struct TcpDialer;
#[async_trait::async_trait]
impl Dialer<SignedTcpAddressingInformation> for TcpDialer {
    type Connection = TcpStream;
    type Error = std::io::Error;
    async fn connect(
        &mut self,
        address: SignedTcpAddressingInformation,
    ) -> Result<Self::Connection, Self::Error> {
        let SignedTcpAddressingInformation {
            addressing_information,
            ..
        } = address;
        let TcpAddressingInformation {
            primary_address,
            other_addresses,
            ..
        } = addressing_information;
        let parsed_addresses: Vec<_> = iter::once(primary_address)
            .chain(other_addresses)
            .filter_map(|address| address.to_socket_addrs().ok())
            .flatten()
            .collect();
        let stream = TcpStream::connect(&parsed_addresses[..]).await?;
        if stream.set_linger(None).is_err() {
            info!(target: LOG_TARGET, "stream.set_linger(None) failed.");
        };
        Ok(stream)
    }
}
#[derive(Debug)]
pub enum Error {
    Io(IoError),
    AddressingInformation(AddressingInformationError),
}
impl From<IoError> for Error {
    fn from(e: IoError) -> Self {
        Error::Io(e)
    }
}
impl From<AddressingInformationError> for Error {
    fn from(e: AddressingInformationError) -> Self {
        Error::AddressingInformation(e)
    }
}
pub async fn new_tcp_network<A: ToSocketAddrs>(
    listening_addresses: A,
    external_addresses: Vec<String>,
    authority_pen: &AuthorityPen,
) -> Result<
    (
        impl Dialer<SignedTcpAddressingInformation>,
        impl Listener,
        impl NetworkIdentity<
            AddressingInformation = SignedTcpAddressingInformation,
            PeerId = AuthorityIdWrapper,
        >,
    ),
    Error,
> {
    let listener = TcpListener::bind(listening_addresses).await?;
    let identity = SignedTcpAddressingInformation::new(external_addresses, authority_pen)?;
    Ok((TcpDialer {}, listener, identity))
}
#[cfg(test)]
pub mod testing {
    use super::{AuthorityIdWrapper, SignedTcpAddressingInformation};
    use crate::{crypto::AuthorityPen, network::NetworkIdentity};
    pub fn new_identity(
        external_addresses: Vec<String>,
        authority_pen: &AuthorityPen,
    ) -> impl NetworkIdentity<
        AddressingInformation = SignedTcpAddressingInformation,
        PeerId = AuthorityIdWrapper,
    > {
        SignedTcpAddressingInformation::new(external_addresses, authority_pen)
            .expect("the provided addresses are fine")
    }
}