mod config;
mod http;
mod peer;
mod udp;

use peer::Peer;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    use clap::Parser;
    use colored::Colorize;
    use config::Config;
    use std::time::Duration;

    fn stats(interval: u32, leechers: u32, seeders: u32, peers: Vec<Peer>) {
        println!("{}", format!("Update interval: {interval}s").yellow());
        println!("{}: {seeders}, {}: {leechers}", "S".green(), "L".red());
        if peers.is_empty() {
            println!("{}", "No peers.".red());
        } else {
            for peer in peers {
                println!("{peer}")
            }
        }
    }

    let config = Config::parse();

    if config.http_tracker.as_ref().is_none_or(|h| h.is_empty())
        && config.udp_tracker.as_ref().is_none_or(|u| u.is_empty())
    {
        println!("{}", "Provide at least one HTTP or UDP tracker(s)".red());
        std::process::exit(1);
    }

    if let Some(http_tracker) = config.http_tracker {
        for url in http_tracker {
            let query = http::query::Announce::new(url.as_str(), &config.info_hash, config.port)?;
            println!(
                "{}",
                format!("Sending HTTP announce to `{query}`...").blue()
            );
            let mut peers = peer::new_buffer(Some(config.peers_buffer_capacity));
            match http::announce(
                &query,
                Duration::from_secs(config.timeout),
                config.proxy_url.as_deref(),
                Some(&mut peers),
            )
            .await
            {
                Ok(response) => stats(
                    response.interval,
                    response.leechers,
                    response.seeders,
                    peers,
                ),
                Err(e) => println!("{}", e.to_string().red()),
            }
        }
    }

    if let Some(udp_tracker) = config.udp_tracker {
        use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, UdpSocket};
        use udp::Server;

        let timeout = Duration::from_secs(config.timeout);

        for address in udp_tracker {
            let server = if address.is_ipv6() {
                UdpSocket::bind(SocketAddr::V6(SocketAddrV6::new(
                    Ipv6Addr::UNSPECIFIED,
                    config.port,
                    0,
                    0,
                )))?
            } else {
                UdpSocket::bind(SocketAddr::V4(SocketAddrV4::new(
                    Ipv4Addr::UNSPECIFIED,
                    config.port,
                )))?
            };
            server.set_read_timeout(Some(timeout))?;
            server.set_write_timeout(Some(timeout))?;

            println!(
                "{}",
                format!("Sending UDP announce to `{address}`...").blue()
            );
            let mut peers = peer::new_buffer(Some(config.peers_buffer_capacity));

            match server.announce(&config.info_hash, &address, Some(&mut peers)) {
                Ok(response) => stats(
                    response.interval,
                    response.leechers,
                    response.seeders,
                    peers,
                ),
                Err(e) => println!("{}", e.to_string().red()),
            }
        }
    }

    Ok(())
}
