Toy implementation of the gemini protocol
use std::{
    io::{Read, Write},
    net::TcpStream,
    sync::Arc,
};

use rustls::{client::ServerCertVerifier, ClientConnection, ServerName};
use url::Url;

pub trait Network {
    type Error;
    type Connection: Read + Write;
    fn connect(&mut self, url: &Url) -> Result<Self::Connection, Self::Error>;
}

pub struct TcpNetwork<V> {
    _verifier: V,
}

impl<V> TcpNetwork<V> {
    pub fn new(ver: V) -> Self {
        Self { _verifier: ver }
    }
}

impl<V: ServerCertVerifier + Default + 'static> Network for TcpNetwork<V> {
    type Error = !;
    type Connection = rustls::StreamOwned<ClientConnection, TcpStream>;

    fn connect(&mut self, url: &Url) -> Result<Self::Connection, Self::Error> {
        Ok(connect::<V>(&url))
    }
}

fn connect<V: Default + ServerCertVerifier + 'static>(
    url: &Url,
) -> rustls::StreamOwned<ClientConnection, TcpStream> {
    let host = url.host().expect("");
    let sn = server(host);
    let stream = {
        let cfg = tls_cfg(V::default());
        let conn = {
            let client = ClientConnection::new(cfg, sn).expect("could not connect");
            client
        };
        let addrs = url
            .socket_addrs(|| Some(1965))
            .expect("could not get addresses for URL");
        let addr = addrs.get(0).expect("no addresses");
        let sock = TcpStream::connect(addr).expect("failed to connect");
        let stream = rustls::StreamOwned { conn, sock };
        stream
    };
    stream
}

fn server(host: url::Host<&str>) -> ServerName {
    let url::Host::Domain(s) = host
    else {unreachable!("the url is always a string")};
    let sn = s.try_into().expect("this should be a valid DNS name");
    sn
}

fn tls_cfg<V: ServerCertVerifier + 'static>(v: V) -> Arc<rustls::ClientConfig> {
    let root_store = rustls::RootCertStore::empty();
    let cfg = {
        let mut cfg = rustls::ClientConfig::builder()
            .with_safe_defaults()
            .with_root_certificates(root_store)
            .with_no_client_auth();
        cfg.dangerous().set_certificate_verifier(Arc::new(v));
        Arc::new(cfg)
    };
    cfg
}