#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]

use crate::netmask;
use std::net::Ipv4Addr;
use std::ops::{Add, Sub};
use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH};
use ipnet::Ipv4Net;
use crate::cert::{CertificateValidity, deserialize_ed25519_private, deserialize_ed25519_public, deserialize_nebula_certificate, deserialize_nebula_certificate_from_pem, deserialize_x25519_private, deserialize_x25519_public, NebulaCertificate, NebulaCertificateDetails, serialize_ed25519_private, serialize_ed25519_public, serialize_x25519_private, serialize_x25519_public};
use std::str::FromStr;
use ed25519_dalek::{SigningKey, VerifyingKey};
use quick_protobuf::{MessageWrite, Writer};
use rand::rngs::OsRng;
use crate::ca::{NebulaCAPool};
use crate::cert_codec::{RawNebulaCertificate, RawNebulaCertificateDetails};

/// This is a cert that we (e3team) actually use in production, and it's a known-good certificate.
pub const KNOWN_GOOD_CERT: &[u8; 258] = b"-----BEGIN NEBULA CERTIFICATE-----\nCkkKF2UzdGVhbSBJbnRlcm5hbCBOZXR3b3JrKJWev5wGMJWFxKsGOiCvpwoHyKY5\n8Q5+2XxDjtoCf/zlNY/EUdB8bwXQSwEo50ABEkB0Dx76lkMqc3IyH5+ml2dKjTyv\nB4Jiw6x3abf5YZcf8rDuVEgQpvFdJmo3xJyIb3C9vKZ6kXsUxjw6s1JdWgkA\n-----END NEBULA CERTIFICATE-----";

#[test]
fn certificate_serialization() {
    let before = round_systime_to_secs(SystemTime::now() - Duration::from_secs(60)).unwrap();
    let after = round_systime_to_secs(SystemTime::now() + Duration::from_secs(60)).unwrap();
    let pub_key = b"1234567890abcedfghij1234567890ab";

    let cert = NebulaCertificate {
        details: NebulaCertificateDetails {
            name: "testing".to_string(),
            ips: vec![
                netmask!("10.1.1.1", "255.255.255.0"),
                netmask!("10.1.1.2", "255.255.0.0"),
                netmask!("10.1.1.3", "255.0.0.0")
            ],
            subnets: vec![
                netmask!("9.1.1.1", "255.255.255.128"),
                netmask!("9.1.1.2", "255.255.255.0"),
                netmask!("9.1.1.3", "255.255.0.0")
            ],
            groups: vec!["test-group1".to_string(), "test-group2".to_string(), "test-group3".to_string()],
            not_before: before,
            not_after: after,
            public_key: *pub_key,
            is_ca: false,
            issuer: "1234567890abcedfabcd1234567890ab".to_string(),
        },
        signature: b"1234567890abcedfghij1234567890ab".to_vec(),
    };

    let bytes = cert.serialize().unwrap();

    let deserialized = deserialize_nebula_certificate(&bytes).unwrap();

    assert_eq!(cert.signature, deserialized.signature);
    assert_eq!(cert.details.name, deserialized.details.name);
    assert_eq!(cert.details.not_before, deserialized.details.not_before);
    assert_eq!(cert.details.not_after, deserialized.details.not_after);
    assert_eq!(cert.details.public_key, deserialized.details.public_key);
    assert_eq!(cert.details.is_ca, deserialized.details.is_ca);

    assert_eq!(cert.details.ips.len(), deserialized.details.ips.len());
    for item in &cert.details.ips {
        assert!(deserialized.details.ips.contains(item), "deserialized does not contain from source");
    }

    assert_eq!(cert.details.subnets.len(), deserialized.details.subnets.len());
    for item in &cert.details.subnets {
        assert!(deserialized.details.subnets.contains(item), "deserialized does not contain from source");
    }

    assert_eq!(cert.details.groups.len(), deserialized.details.groups.len());
    for item in &cert.details.groups {
        assert!(deserialized.details.groups.contains(item), "deserialized does not contain from source");
    }
}

#[test]
fn certificate_serialization_pem() {
    let before = round_systime_to_secs(SystemTime::now() - Duration::from_secs(60)).unwrap();
    let after = round_systime_to_secs(SystemTime::now() + Duration::from_secs(60)).unwrap();
    let pub_key = b"1234567890abcedfghij1234567890ab";

    let cert = NebulaCertificate {
        details: NebulaCertificateDetails {
            name: "testing".to_string(),
            ips: vec![
                netmask!("10.1.1.1", "255.255.255.0"),
                netmask!("10.1.1.2", "255.255.0.0"),
                netmask!("10.1.1.3", "255.0.0.0")
            ],
            subnets: vec![
                netmask!("9.1.1.1", "255.255.255.128"),
                netmask!("9.1.1.2", "255.255.255.0"),
                netmask!("9.1.1.3", "255.255.0.0")
            ],
            groups: vec!["test-group1".to_string(), "test-group2".to_string(), "test-group3".to_string()],
            not_before: before,
            not_after: after,
            public_key: *pub_key,
            is_ca: false,
            issuer: "1234567890abcedfabcd1234567890ab".to_string(),
        },
        signature: b"1234567890abcedfghij1234567890ab".to_vec(),
    };

    let bytes = cert.serialize_to_pem().unwrap();

    let deserialized = deserialize_nebula_certificate_from_pem(&bytes).unwrap();

    assert_eq!(cert.signature, deserialized.signature);
    assert_eq!(cert.details.name, deserialized.details.name);
    assert_eq!(cert.details.not_before, deserialized.details.not_before);
    assert_eq!(cert.details.not_after, deserialized.details.not_after);
    assert_eq!(cert.details.public_key, deserialized.details.public_key);
    assert_eq!(cert.details.is_ca, deserialized.details.is_ca);

    assert_eq!(cert.details.ips.len(), deserialized.details.ips.len());
    for item in &cert.details.ips {
        assert!(deserialized.details.ips.contains(item), "deserialized does not contain from source");
    }

    assert_eq!(cert.details.subnets.len(), deserialized.details.subnets.len());
    for item in &cert.details.subnets {
        assert!(deserialized.details.subnets.contains(item), "deserialized does not contain from source");
    }

    assert_eq!(cert.details.groups.len(), deserialized.details.groups.len());
    for item in &cert.details.groups {
        assert!(deserialized.details.groups.contains(item), "deserialized does not contain from source");
    }
}

#[test]
fn cert_signing() {
    let before = round_systime_to_secs(SystemTime::now() - Duration::from_secs(60)).unwrap();
    let after = round_systime_to_secs(SystemTime::now() + Duration::from_secs(60)).unwrap();
    let pub_key = b"1234567890abcedfghij1234567890ab";

    let mut cert = NebulaCertificate {
        details: NebulaCertificateDetails {
            name: "testing".to_string(),
            ips: vec![
                netmask!("10.1.1.1", "255.255.255.0"),
                netmask!("10.1.1.2", "255.255.0.0"),
                netmask!("10.1.1.3", "255.0.0.0")
            ],
            subnets: vec![
                netmask!("9.1.1.1", "255.255.255.128"),
                netmask!("9.1.1.2", "255.255.255.0"),
                netmask!("9.1.1.3", "255.255.0.0")
            ],
            groups: vec!["test-group1".to_string(), "test-group2".to_string(), "test-group3".to_string()],
            not_before: before,
            not_after: after,
            public_key: *pub_key,
            is_ca: false,
            issuer: "1234567890abcedfabcd1234567890ab".to_string(),
        },
        signature: b"1234567890abcedfghij1234567890ab".to_vec(),
    };

    let mut csprng = OsRng;
    let key = SigningKey::generate(&mut csprng);

    assert!(cert.check_signature(&key.verifying_key()).is_err());
    cert.sign(&key).unwrap();
    assert!(cert.check_signature(&key.verifying_key()).unwrap());
}

#[test]
fn cert_expiry() {
    let cert = NebulaCertificate {
        details: NebulaCertificateDetails {
            name: String::new(),
            ips: vec![],
            subnets: vec![],
            groups: vec![],
            not_before: SystemTime::now().sub(Duration::from_secs(60)),
            not_after: SystemTime::now().add(Duration::from_secs(60)),
            public_key: [0u8; 32],
            is_ca: false,
            issuer: String::new(),
        },
        signature: vec![],
    };

    assert!(cert.expired(SystemTime::now().add(Duration::from_secs(60 * 60 * 60))));
    assert!(cert.expired(SystemTime::now().sub(Duration::from_secs(60 * 60 * 60))));
    assert!(!cert.expired(SystemTime::now()));
}

#[test]
fn cert_display() {
    let cert = NebulaCertificate {
        details: NebulaCertificateDetails {
            name: String::new(),
            ips: vec![],
            subnets: vec![],
            groups: vec![],
            not_before: SystemTime::now().sub(Duration::from_secs(60)),
            not_after: SystemTime::now().add(Duration::from_secs(60)),
            public_key: [0u8; 32],
            is_ca: false,
            issuer: String::new(),
        },
        signature: vec![],
    };
    println!("{cert}");
}

#[test]
#[should_panic]
fn cert_deserialize_empty_bytes() {
    deserialize_nebula_certificate(&[]).unwrap();
}

#[test]
fn cert_deserialize_unpaired_ips() {
    let broken_cert = RawNebulaCertificate {
        Details: Some(RawNebulaCertificateDetails {
            Name: String::new(),
            Ips: vec![0],
            Subnets: vec![],
            Groups: vec![],
            NotBefore: 0,
            NotAfter: 0,
            PublicKey: vec![],
            IsCA: false,
            Issuer: vec![],
        }),
        Signature: vec![],
    };
    let mut bytes = vec![];
    let mut writer = Writer::new(&mut bytes);
    broken_cert.write_message(&mut writer).unwrap();

    deserialize_nebula_certificate(&bytes).unwrap_err();
}

#[test]
fn cert_deserialize_unpaired_subnets() {
    let broken_cert = RawNebulaCertificate {
        Details: Some(RawNebulaCertificateDetails {
            Name: String::new(),
            Ips: vec![],
            Subnets: vec![0],
            Groups: vec![],
            NotBefore: 0,
            NotAfter: 0,
            PublicKey: vec![],
            IsCA: false,
            Issuer: vec![],
        }),
        Signature: vec![],
    };
    let mut bytes = vec![];
    let mut writer = Writer::new(&mut bytes);
    broken_cert.write_message(&mut writer).unwrap();

    deserialize_nebula_certificate(&bytes).unwrap_err();
}

#[test]
fn cert_deserialize_wrong_pubkey_len() {
    let broken_cert = RawNebulaCertificate {
        Details: Some(RawNebulaCertificateDetails {
            Name: String::new(),
            Ips: vec![],
            Subnets: vec![],
            Groups: vec![],
            NotBefore: 0,
            NotAfter: 0,
            PublicKey: vec![0u8; 31],
            IsCA: false,
            Issuer: vec![],
        }),
        Signature: vec![],
    };
    let mut bytes = vec![];
    let mut writer = Writer::new(&mut bytes);
    broken_cert.write_message(&mut writer).unwrap();

    deserialize_nebula_certificate(&bytes).unwrap_err();

    assert!(deserialize_nebula_certificate_from_pem(&[0u8; 32]).is_err());
}

#[test]
fn x25519_serialization() {
    let bytes = [0u8; 32];
    assert_eq!(deserialize_x25519_private(&serialize_x25519_private(&bytes)).unwrap(), bytes);
    assert!(deserialize_x25519_private(&[0u8; 32]).is_err());
    assert_eq!(deserialize_x25519_public(&serialize_x25519_public(&bytes)).unwrap(), bytes);
    assert!(deserialize_x25519_public(&[0u8; 32]).is_err());
}

#[test]
fn ed25519_serialization() {
    let bytes = [0u8; 64];
    assert_eq!(deserialize_ed25519_private(&serialize_ed25519_private(&bytes)).unwrap(), bytes);
    assert!(deserialize_ed25519_private(&[0u8; 32]).is_err());
    assert_eq!(deserialize_ed25519_public(&serialize_ed25519_public(&bytes)).unwrap(), bytes);
    assert!(deserialize_ed25519_public(&[0u8; 32]).is_err());
}

#[test]
fn cert_verify() {
    let (ca_cert, ca_key, _ca_pub) = test_ca_cert(round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 10)).unwrap(), vec![], vec![], vec!["groupa".to_string()]);

    let (cert, _, _) = test_cert(&ca_cert, &ca_key, SystemTime::now(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![], vec![], vec![]);

    let mut ca_pool = NebulaCAPool::new();
    ca_pool.add_ca_certificate(&ca_cert.serialize_to_pem().unwrap()).unwrap();

    let fingerprint = cert.sha256sum().unwrap();
    ca_pool.blocklist_fingerprint(&fingerprint);

    assert!(matches!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Blocklisted));

    ca_pool.reset_blocklist();

    assert!(matches!(cert.verify(SystemTime::now() + Duration::from_secs(60 * 60 * 60), &ca_pool).unwrap(), CertificateValidity::RootCertExpired));
    assert!(matches!(cert.verify(SystemTime::now() + Duration::from_secs(60 * 60 * 6), &ca_pool).unwrap(), CertificateValidity::CertExpired));

    let (cert_with_bad_group, _, _) = test_cert(&ca_cert, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), in_a_minute(), vec![], vec![], vec!["group-not-present on parent".to_string()]);
    assert_eq!(cert_with_bad_group.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::GroupNotPresentOnSigner);

    let (cert_with_good_group, _, _) = test_cert(&ca_cert, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), in_a_minute(), vec![], vec![], vec!["groupa".to_string()]);
    assert_eq!(cert_with_good_group.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Ok);

}

#[test]
fn cert_verify_ip() {
    let ca_ip_1 = Ipv4Net::from_str("10.0.0.0/16").unwrap();
    let ca_ip_2 = Ipv4Net::from_str("192.168.0.0/24").unwrap();

    let (ca, ca_key, _ca_pub) = test_ca_cert(round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 10)).unwrap(), vec![ca_ip_1, ca_ip_2], vec![], vec![]);

    let ca_pem = ca.serialize_to_pem().unwrap();

    let mut ca_pool = NebulaCAPool::new();
    ca_pool.add_ca_certificate(&ca_pem).unwrap();

    // ip is outside the network
    let cip1 = netmask!("10.1.0.0", "255.255.255.0");
    let cip2 = netmask!("192.198.0.1", "255.255.0.0");
    let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![cip1, cip2], vec![], vec![]);

    assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::IPNotPresentOnSigner);

    // ip is outside the network - reversed order from above
    let cip1 = netmask!("192.198.0.1", "255.255.255.0");
    let cip2 = netmask!("10.1.0.0", "255.255.255.0");
    let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![cip1, cip2], vec![], vec![]);

    assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::IPNotPresentOnSigner);

    // ip is within the network but mask is outside
    let cip1 = netmask!("10.0.1.0", "255.254.0.0");
    let cip2 = netmask!("192.168.0.1", "255.255.255.0");
    let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![cip1, cip2], vec![], vec![]);

    assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::IPNotPresentOnSigner);

    // ip is within the network but mask is outside - reversed order from above
    let cip1 = netmask!("192.168.0.1", "255.255.255.0");
    let cip2 = netmask!("10.0.1.0", "255.254.0.0");
    let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![cip1, cip2], vec![], vec![]);

    assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::IPNotPresentOnSigner);

    // ip and mask are within the network
    let cip1 = netmask!("10.0.1.0", "255.255.0.0");
    let cip2 = netmask!("192.168.0.1", "255.255.255.128");
    let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![cip1, cip2], vec![], vec![]);

    assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Ok);

    // Exact matches
    let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![ca_ip_1, ca_ip_2], vec![], vec![]);
    assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Ok);

    // Exact matches reversed
    let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![ca_ip_2, ca_ip_1], vec![], vec![]);
    assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Ok);

    // Exact matches reversed with just one
    let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![ca_ip_2], vec![], vec![]);
    assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Ok);
}

#[test]
fn cert_verify_subnet() {
    let ca_ip_1 = Ipv4Net::from_str("10.0.0.0/16").unwrap();
    let ca_ip_2 = Ipv4Net::from_str("192.168.0.0/24").unwrap();

    let (ca, ca_key, _ca_pub) = test_ca_cert(round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 10)).unwrap(), vec![],vec![ca_ip_1, ca_ip_2], vec![]);

    let ca_pem = ca.serialize_to_pem().unwrap();

    let mut ca_pool = NebulaCAPool::new();
    ca_pool.add_ca_certificate(&ca_pem).unwrap();

    // ip is outside the network
    let cip1 = netmask!("10.1.0.0", "255.255.255.0");
    let cip2 = netmask!("192.198.0.1", "255.255.0.0");
    let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![],vec![cip1, cip2], vec![]);

    assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::SubnetNotPresentOnSigner);

    // ip is outside the network - reversed order from above
    let cip1 = netmask!("192.198.0.1", "255.255.255.0");
    let cip2 = netmask!("10.1.0.0", "255.255.255.0");
    let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![],vec![cip1, cip2], vec![]);

    assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::SubnetNotPresentOnSigner);

    // ip is within the network but mask is outside
    let cip1 = netmask!("10.0.1.0", "255.254.0.0");
    let cip2 = netmask!("192.168.0.1", "255.255.255.0");
    let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![],vec![cip1, cip2], vec![]);

    assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::SubnetNotPresentOnSigner);

    // ip is within the network but mask is outside - reversed order from above
    let cip1 = netmask!("192.168.0.1", "255.255.255.0");
    let cip2 = netmask!("10.0.1.0", "255.254.0.0");
    let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![cip1, cip2], vec![], vec![]);

    assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::SubnetNotPresentOnSigner);

    // ip and mask are within the network
    let cip1 = netmask!("10.0.1.0", "255.255.0.0");
    let cip2 = netmask!("192.168.0.1", "255.255.255.128");
    let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![], vec![cip1, cip2], vec![]);

    assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Ok);

    // Exact matches
    let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![],vec![ca_ip_1, ca_ip_2], vec![]);
    assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Ok);

    // Exact matches reversed
    let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![], vec![ca_ip_2, ca_ip_1], vec![]);
    assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Ok);

    // Exact matches reversed with just one
    let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![], vec![ca_ip_2], vec![]);
    assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Ok);
}

#[test]
fn cert_private_key() {
    let (ca, ca_key, _) = test_ca_cert(SystemTime::now(), SystemTime::now(), vec![], vec![], vec![]);
    ca.verify_private_key(&ca_key.to_keypair_bytes()).unwrap();

    let (_, ca_key2, _) = test_ca_cert(SystemTime::now(), SystemTime::now(), vec![], vec![], vec![]);
    ca.verify_private_key(&ca_key2.to_keypair_bytes()).unwrap_err();

    let (cert, priv_key, _) = test_cert(&ca, &ca_key, SystemTime::now(), SystemTime::now(), vec![], vec![], vec![]);
    cert.verify_private_key(&priv_key.to_bytes()).unwrap();

    let (cert2, _, _) = test_cert(&ca, &ca_key, SystemTime::now(), SystemTime::now(), vec![], vec![], vec![]);
    cert2.verify_private_key(&priv_key.to_bytes()).unwrap_err();
}

#[test]
fn capool_from_pem() {
    let no_newlines = b"# Current provisional, Remove once everything moves over to the real root.
-----BEGIN NEBULA CERTIFICATE-----
CkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL
vcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv
bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
-----END NEBULA CERTIFICATE-----
# root-ca01
-----BEGIN NEBULA CERTIFICATE-----
CkMKEW5lYnVsYSByb290IGNhIDAxKJL2u9EFMJL86+cGOiDPXMH4oU6HZTk/CqTG
BVG+oJpAoqokUBbI4U0N8CSfpUABEkB/Pm5A2xyH/nc8mg/wvGUWG3pZ7nHzaDMf
8/phAUt+FLzqTECzQKisYswKvE3pl9mbEYKbOdIHrxdIp95mo4sF
-----END NEBULA CERTIFICATE-----";
    let with_newlines = b"# Current provisional, Remove once everything moves over to the real root.
-----BEGIN NEBULA CERTIFICATE-----
CkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL
vcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv
bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
-----END NEBULA CERTIFICATE-----
# root-ca01
-----BEGIN NEBULA CERTIFICATE-----
CkMKEW5lYnVsYSByb290IGNhIDAxKJL2u9EFMJL86+cGOiDPXMH4oU6HZTk/CqTG
BVG+oJpAoqokUBbI4U0N8CSfpUABEkB/Pm5A2xyH/nc8mg/wvGUWG3pZ7nHzaDMf
8/phAUt+FLzqTECzQKisYswKvE3pl9mbEYKbOdIHrxdIp95mo4sF
-----END NEBULA CERTIFICATE-----

";
    let expired = b"# expired certificate
-----BEGIN NEBULA CERTIFICATE-----
CjkKB2V4cGlyZWQouPmWjQYwufmWjQY6ILCRaoCkJlqHgv5jfDN4lzLHBvDzaQm4
vZxfu144hmgjQAESQG4qlnZi8DncvD/LDZnLgJHOaX1DWCHHEh59epVsC+BNgTie
WH1M9n4O7cFtGlM6sJJOS+rCVVEJ3ABS7+MPdQs=
-----END NEBULA CERTIFICATE-----";

    let pool_a = NebulaCAPool::new_from_pem(no_newlines).unwrap();
    assert_eq!(pool_a.cas["c9bfaf7ce8e84b2eeda2e27b469f4b9617bde192efd214b68891ecda6ed49522"].details.name, "nebula root ca".to_string());
    assert_eq!(pool_a.cas["5c9c3f23e7ee7fe97637cbd3a0a5b854154d1d9aaaf7b566a51f4a88f76b64cd"].details.name, "nebula root ca 01".to_string());
    assert!(!pool_a.expired);

    let pool_b = NebulaCAPool::new_from_pem(with_newlines).unwrap();
    assert_eq!(pool_b.cas["c9bfaf7ce8e84b2eeda2e27b469f4b9617bde192efd214b68891ecda6ed49522"].details.name, "nebula root ca".to_string());
    assert_eq!(pool_b.cas["5c9c3f23e7ee7fe97637cbd3a0a5b854154d1d9aaaf7b566a51f4a88f76b64cd"].details.name, "nebula root ca 01".to_string());
    assert!(!pool_b.expired);

    let pool_c = NebulaCAPool::new_from_pem(expired).unwrap();
    assert!(pool_c.expired);
    assert_eq!(pool_c.cas["152070be6bb19bc9e3bde4c2f0e7d8f4ff5448b4c9856b8eccb314fade0229b0"].details.name, "expired");

    let mut pool_d = NebulaCAPool::new_from_pem(with_newlines).unwrap();
    pool_d.add_ca_certificate(expired).unwrap();
    assert_eq!(pool_d.cas["c9bfaf7ce8e84b2eeda2e27b469f4b9617bde192efd214b68891ecda6ed49522"].details.name, "nebula root ca".to_string());
    assert_eq!(pool_d.cas["5c9c3f23e7ee7fe97637cbd3a0a5b854154d1d9aaaf7b566a51f4a88f76b64cd"].details.name, "nebula root ca 01".to_string());
    assert_eq!(pool_d.cas["152070be6bb19bc9e3bde4c2f0e7d8f4ff5448b4c9856b8eccb314fade0229b0"].details.name, "expired");
    assert!(pool_d.expired);
    assert_eq!(pool_d.get_fingerprints().len(), 3);
}

#[macro_export]
macro_rules! netmask {
    ($ip:expr,$mask:expr) => {
        Ipv4Net::with_netmask(Ipv4Addr::from_str($ip).unwrap(), Ipv4Addr::from_str($mask).unwrap()).unwrap()
    };
}

fn round_systime_to_secs(time: SystemTime) -> Result<SystemTime, SystemTimeError> {
    let secs = time.duration_since(UNIX_EPOCH)?.as_secs();
    Ok(SystemTime::UNIX_EPOCH.add(Duration::from_secs(secs)))
}

fn test_ca_cert(before: SystemTime, after: SystemTime, ips: Vec<Ipv4Net>, subnets: Vec<Ipv4Net>, groups: Vec<String>) -> (NebulaCertificate, SigningKey, VerifyingKey) {
    let mut csprng = OsRng;
    let key = SigningKey::generate(&mut csprng);
    let pub_key = key.verifying_key();

    let mut cert = NebulaCertificate {
        details: NebulaCertificateDetails {
            name: "TEST_CA".to_string(),
            ips,
            subnets,
            groups,
            not_before: before,
            not_after: after,
            public_key: pub_key.to_bytes(),
            is_ca: true,
            issuer: String::new(),
        },
        signature: vec![],
    };
    cert.sign(&key).unwrap();
    (cert, key, pub_key)
}

#[test]
fn test_deserialize_ed25519_private() {
    let priv_key = b"-----BEGIN NEBULA ED25519 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-----END NEBULA ED25519 PRIVATE KEY-----";
    let short_key = b"-----BEGIN NEBULA ED25519 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-----END NEBULA ED25519 PRIVATE KEY-----";
    let invalid_banner = b"-----BEGIN NOT A NEBULA PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-----END NOT A NEBULA PRIVATE KEY-----";
    let invalid_pem = b"-BEGIN NEBULA ED25519 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-END NEBULA ED25519 PRIVATE KEY-----";

    deserialize_ed25519_private(priv_key).unwrap();

    deserialize_ed25519_private(short_key).unwrap_err();

    deserialize_ed25519_private(invalid_banner).unwrap_err();

    deserialize_ed25519_private(invalid_pem).unwrap_err();
}

#[test]
fn test_deserialize_x25519_private() {
    let priv_key = b"-----BEGIN NEBULA X25519 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----END NEBULA X25519 PRIVATE KEY-----";
    let short_key = b"-----BEGIN NEBULA X25519 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-----END NEBULA X25519 PRIVATE KEY-----";
    let invalid_banner = b"-----BEGIN NOT A NEBULA PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----END NOT A NEBULA PRIVATE KEY-----";
    let invalid_pem = b"-BEGIN NEBULA X25519 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-END NEBULA X25519 PRIVATE KEY-----";

    deserialize_x25519_private(priv_key).unwrap();

    deserialize_x25519_private(short_key).unwrap_err();

    deserialize_x25519_private(invalid_banner).unwrap_err();

    deserialize_x25519_private(invalid_pem).unwrap_err();
}

#[test]
fn test_pem_deserialization() {
    let good_cert = b"# A good cert
-----BEGIN NEBULA CERTIFICATE-----
CkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL
vcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv
bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
-----END NEBULA CERTIFICATE-----";
    let bad_banner = b"-----BEGIN NOT A NEBULA CERTIFICATE-----
CkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL
vcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv
bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
-----END NOT A NEBULA CERTIFICATE-----";
    let invalid_pem = b"# Not a valid PEM format
-BEGIN NEBULA CERTIFICATE-----
CkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL
vcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv
bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
-END NEBULA CERTIFICATE----";

    // success
    deserialize_nebula_certificate_from_pem(good_cert).unwrap();

    // fail because invalid banner
    deserialize_nebula_certificate_from_pem(bad_banner).unwrap_err();

    // fail because nonsense pem
    deserialize_nebula_certificate_from_pem(invalid_pem).unwrap_err();
}

#[test]
fn test_deserialize_ed25519_public() {
    let priv_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-----END NEBULA ED25519 PUBLIC KEY-----";
    let short_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-----END NEBULA ED25519 PUBLIC KEY-----";
    let invalid_banner = b"-----BEGIN NOT A NEBULA PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-----END NOT A NEBULA PUBLIC KEY-----";
    let invalid_pem = b"-BEGIN NEBULA ED25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-END NEBULA ED25519 PUBLIC KEY-----";

    deserialize_ed25519_public(priv_key).unwrap();

    deserialize_ed25519_public(short_key).unwrap_err();

    deserialize_ed25519_public(invalid_banner).unwrap_err();

    deserialize_ed25519_public(invalid_pem).unwrap_err();
}

#[test]
// Ensure that trifid-pki produces the *exact same* bit-for-bit representation as nebula does
// to ensure signature interoperability
// We use an e3team production certificate for this as it is a known-good certificate.
fn test_serialization_interoperability() {
    let known_good_cert = hex::decode("0a650a08636f72652d74777212098184c4508080f8ff0f28ae9fbf9c06309485c4ab063a20304e6279d8722f0fd8d966faa70adaeec08c4649d486457bb038be243753a7474a2056860ded3d14b7f3b77ca2a062ee5d683dd06b35f87446d8d6a7e923b6a7783d1240eb5d6b688eda0d36e925219c098ff2799e42207a093f7d9b7d875823ca05b2e0d3a749dd2fc9cec811ed9a2865d71c4d53cfdfd1bf7e6d5058bd9ecd388ddf0d").unwrap();

    let cert = deserialize_nebula_certificate(&known_good_cert).unwrap();
    let reserialized_cert_pem = cert.serialize().unwrap();
    assert_eq!(known_good_cert, reserialized_cert_pem);
}

#[test]
fn test_deserialize_x25519_public() {
    let priv_key = b"-----BEGIN NEBULA X25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----END NEBULA X25519 PUBLIC KEY-----";
    let short_key = b"-----BEGIN NEBULA X25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-----END NEBULA X25519 PUBLIC KEY-----";
    let invalid_banner = b"-----BEGIN NOT A NEBULA PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----END NOT A NEBULA PUBLIC KEY-----";
    let invalid_pem = b"-BEGIN NEBULA X25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-END NEBULA X25519 PUBLIC KEY-----";

    deserialize_x25519_public(priv_key).unwrap();

    deserialize_x25519_public(short_key).unwrap_err();

    deserialize_x25519_public(invalid_banner).unwrap_err();

    deserialize_x25519_public(invalid_pem).unwrap_err();
}

#[test]
fn ca_pool_add_non_ca() {
    let mut ca_pool = NebulaCAPool::new();

    let (ca, ca_key, _) = test_ca_cert(SystemTime::now(), SystemTime::now() + Duration::from_secs(3600), vec![], vec![], vec![]);
    let (cert, _, _) = test_cert(&ca, &ca_key, SystemTime::now(), SystemTime::now(), vec![], vec![], vec![]);

    ca_pool.add_ca_certificate(&cert.serialize_to_pem().unwrap()).unwrap_err();
}

fn test_cert(ca: &NebulaCertificate, key: &SigningKey, before: SystemTime, after: SystemTime, ips: Vec<Ipv4Net>, subnets: Vec<Ipv4Net>, groups: Vec<String>) -> (NebulaCertificate, SigningKey, VerifyingKey) {
    let issuer = ca.sha256sum().unwrap();

    let real_groups = if groups.is_empty() {
        vec!["test-group1".to_string(), "test-group2".to_string(), "test-group3".to_string()]
    } else {
        groups
    };

    let real_ips = if ips.is_empty() {
        vec![
            netmask!("10.1.1.1", "255.255.255.0"),
            netmask!("10.1.1.2", "255.255.0.0"),
            netmask!("10.1.1.3", "255.0.0.0")
        ]
    } else {
        ips
    };

    let real_subnets = if subnets.is_empty() {
        vec![
            netmask!("9.1.1.1", "255.255.255.128"),
            netmask!("9.1.1.2", "255.255.255.0"),
            netmask!("9.1.1.3", "255.255.0.0")
        ]
    } else {
        subnets
    };

    let mut csprng = OsRng;
    let cert_key = SigningKey::generate(&mut csprng);
    let pub_key = cert_key.verifying_key();

    let mut cert = NebulaCertificate {
        details: NebulaCertificateDetails {
            name: "TEST_CA".to_string(),
            ips: real_ips,
            subnets: real_subnets,
            groups: real_groups,
            not_before: before,
            not_after: after,
            public_key: pub_key.to_bytes(),
            is_ca: false,
            issuer,
        },
        signature: vec![],
    };
    cert.sign(key).unwrap();
    (cert, cert_key, pub_key)
}

#[allow(dead_code)]
fn in_a_minute() -> SystemTime {
    round_systime_to_secs(SystemTime::now().add(Duration::from_secs(60))).unwrap()
}
#[allow(dead_code)]
fn a_minute_ago() -> SystemTime {
    round_systime_to_secs(SystemTime::now().sub(Duration::from_secs(60))).unwrap()
}