mod response;

use bendy::decoding::FromBencode;
use rand::RngExt;
use std::{
    error::Error,
    net::{Ipv4Addr, Ipv6Addr},
    time::Duration,
};
use url::Url;

pub fn handle(
    info_hash: &[u8; 20],
    mut url: Url,
    port: u16,
    timeout: u64,
) -> Result<(), Box<dyn Error>> {
    fn url_encode_bytes(bytes: &[u8]) -> String {
        bytes.iter().map(|b| format!("%{:02x}", b)).collect()
    }

    let mut rng = rand::rng();
    let mut peer_id = [0u8; 20];
    rng.fill(&mut peer_id);

    url.set_query(Some(&format!(
        "info_hash={}&peer_id={}&port={port}&uploaded=0&downloaded=0&left=1000&compact=1",
        url_encode_bytes(info_hash),
        url_encode_bytes(&peer_id)
    )));

    let target = url.as_str();

    let client = reqwest::blocking::Client::builder()
        .timeout(Duration::from_secs(timeout))
        .build()?;

    println!("Sending HTTP announce to `{target}`...");

    let response = client.get(target).send()?;

    if !response.status().is_success() {
        return Err(format!("Tracker returned status: {}", response.status()).into());
    }

    let response = client.get(target).send()?;

    if !response.status().is_success() {
        return Err(format!("Tracker returned status: {}", response.status()).into());
    }

    let body = response.bytes()?;

    let result = response::Response::from_bencode(&body)?;

    println!("Update interval: {}s", result.interval);
    println!("L: {}, S: {}", result.incomplete, result.complete);

    if !result.peers.is_empty() {
        for chunk in result.peers.chunks_exact(6) {
            let ip_bytes: [u8; 4] = chunk[0..4].try_into()?;
            let ip = Ipv4Addr::from(ip_bytes);
            let port = u16::from_be_bytes(chunk[4..6].try_into()?);
            println!("{ip}:{port}");
        }
    }

    if !result.peers6.is_empty() {
        for chunk in result.peers6.chunks_exact(18) {
            let ip_bytes: [u8; 16] = chunk[0..16].try_into()?;
            let ip = Ipv6Addr::from(ip_bytes);
            let port = u16::from_be_bytes(chunk[16..18].try_into()?);
            println!("[{ip}]:{port}");
        }
    }

    if result.peers.is_empty() && result.peers6.is_empty() {
        println!("No peers.");
    }

    Ok(())
}
