use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::collections::HashMap;

use crate::{Config, Request, Rocket, State};
use crate::http::hyper;

macro_rules! assert_headers {
    ($($key:expr => [$($value:expr),+]),+) => ({
        // Set up the parameters to the hyper request object.
        let h_method = hyper::Method::GET;
        let h_uri = "/test".parse().unwrap();
        let h_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8000);
        let mut h_headers = hyper::HeaderMap::new();

        // Add all of the passed in headers to the request.
        $($(h_headers.append($key, hyper::HeaderValue::from_str($value).unwrap());)+)+

        // Build up what we expect the headers to actually be.
        let mut expected = HashMap::new();
        $(expected.entry($key).or_insert(vec![]).append(&mut vec![$($value),+]);)+

        // Dispatch the request and check that the headers are what we expect.
        let config = Config::development();
        let r = Rocket::custom(config);
        let req = Request::from_hyp(&r, h_method, h_headers, &h_uri, h_addr).unwrap();
        let actual_headers = req.headers();
        for (key, values) in expected.iter() {
            let actual: Vec<_> = actual_headers.get(key).collect();
            assert_eq!(*values, actual);
        }
    })
}

#[test]
fn test_multiple_headers_from_hyp() {
    assert_headers!("friends" => ["alice"]);
    assert_headers!("friends" => ["alice", "bob"]);
    assert_headers!("friends" => ["alice", "bob, carol"]);
    assert_headers!("friends" => ["alice, david", "bob, carol", "eric, frank"]);
    assert_headers!("friends" => ["alice"], "enemies" => ["victor"]);
    assert_headers!("friends" => ["alice", "bob"], "enemies" => ["david", "emily"]);
}

#[test]
fn test_multiple_headers_merge_into_one_from_hyp() {
    assert_headers!("friend" => ["alice"], "friend" => ["bob"]);
    assert_headers!("friend" => ["alice"], "friend" => ["bob"], "friend" => ["carol"]);
    assert_headers!("friend" => ["alice"], "friend" => ["bob"], "enemy" => ["carol"]);
}

struct MyConfig {
    user_val: String,
}

#[tokio::test]
async fn test_can_clone_state() {
    let my_config: MyConfig = MyConfig {
        user_val: "hello".to_string(),
    };

    // Setup our test State
    let mut rocket = Rocket::ignite().manage(my_config);
    let cargo = rocket.inspect().await;
    let my_state: State<'_, MyConfig> = State::from(cargo).unwrap();

    // Now check we can clone it
    let my_clone = my_state.clone();
    assert_eq!(my_clone.user_val, "hello");
}
