use std::{fs, process};
use nwep::{
    Keypair,
    server::{Router, ResponseWriter, Request, ServerBuilder},
};

const DEFAULT_KEY_FILE: &str = "server.key";
const DEFAULT_ADDR: &str = "[::]:6937";

fn load_or_generate_keypair(path: &str) -> Result<Keypair, Box<dyn std::error::Error>> {
    match fs::read_to_string(path) {
        Ok(contents) => {
            let hex = contents.trim();
            if hex.len() != 64 {
                return Err(format!("invalid key file {path}: expected 64 hex chars").into());
            }
            let bytes: Vec<u8> = (0..32)
                .map(|i| u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16))
                .collect::<Result<_, _>>()?;
            let mut seed = [0u8; 32];
            seed.copy_from_slice(&bytes);
            Ok(Keypair::from_seed(&seed)?)
        }
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
            let kp = Keypair::generate()?;
            let hex: String = kp.seed().iter().map(|b| format!("{b:02x}")).collect();
            fs::write(path, format!("{hex}\n"))
                .map_err(|e| format!("saving key file: {e}"))?;
            Ok(kp)
        }
        Err(e) => Err(e.into()),
    }
}

fn main() {
    nwep::init().unwrap_or_else(|e| {
        eprintln!("init: {e}");
        process::exit(1);
    });

    let key_file = std::env::var("NWEP_KEY_FILE").unwrap_or_else(|_| DEFAULT_KEY_FILE.into());
    let keypair = load_or_generate_keypair(&key_file).unwrap_or_else(|e| {
        eprintln!("keypair: {e}");
        process::exit(1);
    });

    let node_id = keypair.node_id().unwrap_or_else(|e| {
        eprintln!("node_id: {e}");
        process::exit(1);
    });
    eprintln!("node id: {node_id}");

    let mut router = Router::new();
    router.handle_func("/hello", |w: &mut ResponseWriter, _r: &Request| {
        let _ = w.respond("ok", b"hello from nwep-rust");
    });

    let addr = std::env::args().nth(1).unwrap_or_else(|| DEFAULT_ADDR.into());

    let (server, event_loop) = ServerBuilder::new(&addr, keypair)
        .on_connect(|info| eprintln!("connected: {}", info.node_id))
        .on_disconnect(|info, code| eprintln!("disconnected: {} (code {code})", info.node_id))
        .build(router)
        .unwrap_or_else(|e| {
            eprintln!("server: {e}");
            process::exit(1);
        });

    let url = server.url("/hello");
    eprintln!("listening on {}", server.addr());
    println!("{url}"); // URL printed to stdout for the client to read

    if let Err(e) = event_loop.run() {
        eprintln!("server run: {e}");
        process::exit(1);
    }
}
