#![allow(missing_docs)]

// Use the `cargo tests` cargo alias and see the `.cargo/config.toml` for more info.

use mac_address::get_mac_address;
use std::net::SocketAddrV4;
use std::sync::Once;
use std::{net::Ipv4Addr, thread};
use toe_beans::v4::*;

static INIT: Once = Once::new();
pub fn initialize() {
    INIT.call_once(|| {
        init_logger(true);
    });
}

fn get_server_config(listen_address: SocketAddrV4) -> ServerConfig {
    ServerConfig {
        socket_config: SocketConfig {
            listen_address,
            interface: Some("lo".to_string()),
        },
        ..ServerConfig::default()
    }
}

fn get_client_config() -> ClientConfig {
    ClientConfig {
        socket_config: SocketConfig {
            interface: Some("lo".to_string()),
            ..SocketConfig::default_client()
        },
        ..ClientConfig::default()
    }
}

#[test]
fn discover_offer() {
    initialize();

    let config = get_server_config("0.0.0.0:8067".parse().unwrap());
    let mut server = Server::new(config);

    // `recv_from` in another thread so that we don't block the client request.
    let handle = thread::spawn(move || {
        server.listen_once();
    });

    let client_config = get_client_config();
    let mut client = Client::new(client_config);
    let message = client.discover();

    client.socket.broadcast(&message, 8067).unwrap();

    let response_message: Message = client.socket.receive().unwrap().0;
    let message_type = response_message
        .find_option(53)
        .expect("Option 53 not in response");

    handle.join().unwrap();

    if let MessageOptions::MessageType(MessageTypes::Offer) = message_type {
    } else {
        panic!("Discover did not receive Offer");
    }
}

#[test]
fn rapid_commit() {
    initialize();

    let config = get_server_config("0.0.0.0:8167".parse().unwrap());
    let mut server = Server::new(config);

    // `recv_from` in another thread so that we don't block the client request.
    let handle = thread::spawn(move || {
        server.listen_once();
    });

    let client_config = get_client_config();
    let mut client = Client::new(client_config);
    let mut message = client.discover();
    message.add_option(MessageOptions::RapidCommit);
    message.add_option(MessageOptions::RequestedIp(AddressOption::new(
        Ipv4Addr::UNSPECIFIED,
    ))); // TODO
    client.socket.broadcast(&message, 8167).unwrap();

    let response_message: Message = client.socket.receive().unwrap().0;
    let message_type = response_message
        .find_option(53)
        .expect("Option 53 not in response");

    handle.join().unwrap();

    if let MessageOptions::MessageType(MessageTypes::Ack) = message_type {
    } else {
        panic!("Discover did not receive Ack");
    }
}

#[test]
fn request_ack() {
    initialize();

    let config = get_server_config("0.0.0.0:8267".parse().unwrap());
    let mut server = Server::new(config);

    let client_config = get_client_config();
    let mut client = Client::new(client_config);
    let discover_message = client.discover();
    let offer_message = server.offer(discover_message).unwrap();

    // `recv_from` in another thread so that we don't block the client request.
    let handle = thread::spawn(move || {
        server.listen_once();
    });

    let message = client.request(offer_message);
    client.socket.broadcast(&message, 8267).unwrap();

    let response_message: Message = client.socket.receive().unwrap().0;
    let message_type = response_message
        .find_option(53)
        .expect("Option 53 not in response");

    handle.join().unwrap();

    if let MessageOptions::MessageType(MessageTypes::Ack) = message_type {
    } else {
        panic!("Request did not receive Ack");
    }
}

#[test]
fn ignores_requests_to_other_servers() {
    initialize();

    let config = get_server_config("0.0.0.0:8367".parse().unwrap());
    let mut server = Server::new(config);

    let client_config = get_client_config();
    let mut client = Client::new(client_config);
    let discover_message = client.discover();
    let offer_message = server.offer(discover_message).unwrap();
    let offered_ip = offer_message.yiaddr;

    assert!(!server.leases.is_available(&offered_ip));

    // `recv_from` in another thread so that we don't block the client request.
    let handle = thread::spawn(move || {
        server.listen_once();
        assert!(server.leases.is_available(&offered_ip));
    });

    let mut message = client.request(offer_message);
    // fake choosing a different dhcp server
    message.options.set(
        2,
        MessageOptions::ServerIdentifier(AddressOption::new(Ipv4Addr::new(127, 0, 0, 2))),
    );

    client.socket.broadcast(&message, 8367).unwrap();

    handle.join().unwrap();
}

#[test]
fn verify_existing_lease() {
    initialize();

    let config = get_server_config("0.0.0.0:8467".parse().unwrap());
    let mut server = Server::new(config);

    let ip = server
        .leases
        .ack(get_mac_address().unwrap().unwrap())
        .unwrap();

    // `recv_from` in another thread so that we don't block the client request.
    let handle = thread::spawn(move || {
        server.listen_once();
    });

    let client_config = get_client_config();
    let mut client = Client::new(client_config);
    let verify_message = client.verify(ip);
    client.socket.broadcast(&verify_message, 8467).unwrap();

    handle.join().unwrap();

    let response_message: Message = client.socket.receive().unwrap().0;
    let message_type = response_message
        .find_option(53)
        .expect("Option 53 not in response");
    if let MessageOptions::MessageType(MessageTypes::Ack) = message_type {
    } else {
        panic!("Discover did not receive Ack");
    }
}

#[test]
fn extend_existing_lease() {
    initialize();

    let config = get_server_config("0.0.0.0:8567".parse().unwrap());
    let mut server = Server::new(config);

    let ip = server
        .leases
        .ack(get_mac_address().unwrap().unwrap())
        .unwrap();

    // `recv_from` in another thread so that we don't block the client request.
    let handle = thread::spawn(move || {
        server.listen_once();
    });

    let client_config = get_client_config();
    let mut client = Client::new(client_config);
    let verify_message = client.extend(ip);
    client.socket.broadcast(&verify_message, 8567).unwrap();

    handle.join().unwrap();

    let response_message: Message = client.socket.receive().unwrap().0;
    let message_type = response_message
        .find_option(53)
        .expect("Option 53 not in response");
    if let MessageOptions::MessageType(MessageTypes::Ack) = message_type {
    } else {
        panic!("Discover did not receive Ack");
    }
}
