//! Test vectors for MetaAddr.

use std::net::SocketAddr;

use chrono::Utc;
use zebra_chain::serialization::{DateTime32, Duration32};

use super::{super::MetaAddr, check};
use crate::{constants::MAX_PEER_ACTIVE_FOR_GOSSIP, protocol::types::PeerServices};

/// Margin of error for time-based tests.
///
/// This is a short duration to consider as error due to a test's execution time when comparing
/// [`DateTime32`]s.
const TEST_TIME_ERROR_MARGIN: Duration32 = Duration32::from_seconds(1);

/// Make sure that the sanitize function handles minimum and maximum times.
#[test]
fn sanitize_extremes() {
    zebra_test::init();

    let min_time_entry = MetaAddr {
        addr: "127.0.0.1:8233".parse().unwrap(),
        services: Default::default(),
        untrusted_last_seen: Some(u32::MIN.into()),
        last_response: Some(u32::MIN.into()),
        last_attempt: None,
        last_failure: None,
        last_connection_state: Default::default(),
    };

    let max_time_entry = MetaAddr {
        addr: "127.0.0.1:8233".parse().unwrap(),
        services: Default::default(),
        untrusted_last_seen: Some(u32::MAX.into()),
        last_response: Some(u32::MAX.into()),
        last_attempt: None,
        last_failure: None,
        last_connection_state: Default::default(),
    };

    if let Some(min_sanitized) = min_time_entry.sanitize() {
        check::sanitize_avoids_leaks(&min_time_entry, &min_sanitized);
    }
    if let Some(max_sanitized) = max_time_entry.sanitize() {
        check::sanitize_avoids_leaks(&max_time_entry, &max_sanitized);
    }
}

/// Test if a newly created local listening address is gossipable.
///
/// The local listener [`MetaAddr`] is always considered gossipable.
#[test]
fn new_local_listener_is_gossipable() {
    zebra_test::init();

    let chrono_now = Utc::now();

    let address = SocketAddr::from(([192, 168, 180, 9], 10_000));
    let peer = MetaAddr::new_local_listener_change(&address)
        .into_new_meta_addr()
        .expect("MetaAddrChange can't create a new MetaAddr");

    assert!(peer.is_active_for_gossip(chrono_now));
}

/// Test if a recently received alternate peer address is not gossipable.
///
/// Such [`MetaAddr`] is only considered gossipable after Zebra has tried to connect to it and
/// confirmed that the address is reachable.
#[test]
fn new_alternate_peer_address_is_not_gossipable() {
    zebra_test::init();

    let chrono_now = Utc::now();

    let address = SocketAddr::from(([192, 168, 180, 9], 10_000));
    let peer = MetaAddr::new_alternate(&address, &PeerServices::NODE_NETWORK)
        .into_new_meta_addr()
        .expect("MetaAddrChange can't create a new MetaAddr");

    assert!(!peer.is_active_for_gossip(chrono_now));
}

/// Test if recently received gossiped peer is gossipable.
#[test]
fn gossiped_peer_reportedly_to_be_seen_recently_is_gossipable() {
    zebra_test::init();

    let chrono_now = Utc::now();

    let address = SocketAddr::from(([192, 168, 180, 9], 10_000));

    // Report last seen within the reachable interval.
    let offset = MAX_PEER_ACTIVE_FOR_GOSSIP
        .checked_sub(TEST_TIME_ERROR_MARGIN)
        .expect("Test margin is too large");
    let last_seen = DateTime32::now()
        .checked_sub(offset)
        .expect("Offset is too large");

    let peer = MetaAddr::new_gossiped_meta_addr(address, PeerServices::NODE_NETWORK, last_seen);

    assert!(peer.is_active_for_gossip(chrono_now));
}

/// Test if received gossiped peer that was reportedly last seen in the future is gossipable.
#[test]
fn gossiped_peer_reportedly_seen_in_the_future_is_gossipable() {
    zebra_test::init();

    let chrono_now = Utc::now();

    let address = SocketAddr::from(([192, 168, 180, 9], 10_000));

    // Report last seen in the future
    let last_seen = DateTime32::now()
        .checked_add(MAX_PEER_ACTIVE_FOR_GOSSIP)
        .expect("Reachable peer duration is too large");

    let peer = MetaAddr::new_gossiped_meta_addr(address, PeerServices::NODE_NETWORK, last_seen);

    assert!(peer.is_active_for_gossip(chrono_now));
}

/// Test if gossiped peer that was reported last seen a long time ago is not gossipable.
#[test]
fn gossiped_peer_reportedly_seen_long_ago_is_not_gossipable() {
    zebra_test::init();

    let chrono_now = Utc::now();

    let address = SocketAddr::from(([192, 168, 180, 9], 10_000));

    // Report last seen just outside the reachable interval.
    let offset = MAX_PEER_ACTIVE_FOR_GOSSIP
        .checked_add(TEST_TIME_ERROR_MARGIN)
        .expect("Test margin is too large");
    let last_seen = DateTime32::now()
        .checked_sub(offset)
        .expect("Offset is too large");

    let peer = MetaAddr::new_gossiped_meta_addr(address, PeerServices::NODE_NETWORK, last_seen);

    assert!(!peer.is_active_for_gossip(chrono_now));
}

/// Test that peer that has just responded is gossipable.
#[test]
fn recently_responded_peer_is_gossipable() {
    zebra_test::init();

    let chrono_now = Utc::now();

    let address = SocketAddr::from(([192, 168, 180, 9], 10_000));
    let peer_seed = MetaAddr::new_alternate(&address, &PeerServices::NODE_NETWORK)
        .into_new_meta_addr()
        .expect("MetaAddrChange can't create a new MetaAddr");

    // Create a peer that has responded
    let peer = MetaAddr::new_responded(&address, &PeerServices::NODE_NETWORK)
        .apply_to_meta_addr(peer_seed)
        .expect("Failed to create MetaAddr for responded peer");

    assert!(peer.is_active_for_gossip(chrono_now));
}

/// Test that peer that last responded in the reachable interval is gossipable.
#[test]
fn not_so_recently_responded_peer_is_still_gossipable() {
    zebra_test::init();

    let chrono_now = Utc::now();

    let address = SocketAddr::from(([192, 168, 180, 9], 10_000));
    let peer_seed = MetaAddr::new_alternate(&address, &PeerServices::NODE_NETWORK)
        .into_new_meta_addr()
        .expect("MetaAddrChange can't create a new MetaAddr");

    // Create a peer that has responded
    let mut peer = MetaAddr::new_responded(&address, &PeerServices::NODE_NETWORK)
        .apply_to_meta_addr(peer_seed)
        .expect("Failed to create MetaAddr for responded peer");

    // Tweak the peer's last response time to be within the limits of the reachable duration
    let offset = MAX_PEER_ACTIVE_FOR_GOSSIP
        .checked_sub(TEST_TIME_ERROR_MARGIN)
        .expect("Test margin is too large");
    let last_response = DateTime32::now()
        .checked_sub(offset)
        .expect("Offset is too large");

    peer.set_last_response(last_response);

    assert!(peer.is_active_for_gossip(chrono_now));
}

/// Test that peer that responded long ago is not gossipable.
#[test]
fn responded_long_ago_peer_is_not_gossipable() {
    zebra_test::init();

    let chrono_now = Utc::now();

    let address = SocketAddr::from(([192, 168, 180, 9], 10_000));
    let peer_seed = MetaAddr::new_alternate(&address, &PeerServices::NODE_NETWORK)
        .into_new_meta_addr()
        .expect("MetaAddrChange can't create a new MetaAddr");

    // Create a peer that has responded
    let mut peer = MetaAddr::new_responded(&address, &PeerServices::NODE_NETWORK)
        .apply_to_meta_addr(peer_seed)
        .expect("Failed to create MetaAddr for responded peer");

    // Tweak the peer's last response time to be outside the limits of the reachable duration
    let offset = MAX_PEER_ACTIVE_FOR_GOSSIP
        .checked_add(TEST_TIME_ERROR_MARGIN)
        .expect("Test margin is too large");
    let last_response = DateTime32::now()
        .checked_sub(offset)
        .expect("Offset is too large");

    peer.set_last_response(last_response);

    assert!(!peer.is_active_for_gossip(chrono_now));
}
