use crate::client::{AppHandle, ClientEvent};
use quinn::{crypto::rustls::QuicClientConfig, rustls::pki_types::CertificateDer};
use std::{
    fs,
    io::ErrorKind,
    net::{IpAddr, Ipv6Addr, SocketAddr},
    str::FromStr,
    sync::Arc,
};
use tokio::sync::mpsc::UnboundedSender;

pub fn connect(handle: AppHandle) {
    std::thread::spawn(|| {
        connect_the(handle).unwrap();
    });
}

pub enum NetCmd {
    SendMsg(String),
}

pub type NetSender = UnboundedSender<NetCmd>;

#[tokio::main]
async fn connect_the(handle: AppHandle) -> anyhow::Result<()> {
    let dirs = directories_next::ProjectDirs::from("", "", "openworm").unwrap();
    let mut roots = quinn::rustls::RootCertStore::empty();
    match fs::read(dirs.data_local_dir().join("cert.der")) {
        Ok(cert) => {
            roots.add(CertificateDer::from(cert))?;
        }
        Err(ref e) if e.kind() == ErrorKind::NotFound => {
            eprintln!("local server certificate not found");
        }
        Err(e) => {
            eprintln!("failed to open local server certificate: {}", e);
        }
    }
    let client_crypto = quinn::rustls::ClientConfig::builder()
        .with_root_certificates(roots)
        .with_no_client_auth();
    let client_config =
        quinn::ClientConfig::new(Arc::new(QuicClientConfig::try_from(client_crypto)?));
    let mut endpoint = quinn::Endpoint::client(SocketAddr::from_str("[::]:0").unwrap())?;
    endpoint.set_default_client_config(client_config);
    let remote = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 4433);
    let host = "localhost";
    let conn = endpoint
        .connect(remote, host)?
        .await
        .map_err(|e| anyhow::anyhow!("failed to connect: {}", e))?;

    let (client_send, mut client_recv) = tokio::sync::mpsc::unbounded_channel::<NetCmd>();

    handle.send(ClientEvent::Connect(client_send));

    while let Some(msg) = client_recv.recv().await {
        match msg {
            NetCmd::SendMsg(content) => {
                let (mut send, recv) = conn
                    .open_bi()
                    .await
                    .map_err(|e| anyhow::anyhow!("failed to open stream: {}", e))?;

                drop(recv);

                send.write_all(content.as_bytes()).await.expect("failed to send");
                send.finish().unwrap();
                send.stopped().await.unwrap();
            }
        }
    }

    Ok(())
}
