use std::{
    net::IpAddr,
    path::PathBuf,
    time::{Duration, SystemTime},
};

use {
    buildstructor::Builder,
    icann_rdap_common::{
        prelude::Numberish,
        response::{
            Autnum, Cidr0Cidr, Domain, Entity, GetSelfLink, Nameserver, Network, RdapResponse,
            SelfLink,
        },
    },
    ipnet::{IpNet, Ipv4Subnets, Ipv6Subnets},
    serde::{Deserialize, Serialize},
    serde_json::Value,
    tokio::time::sleep,
    tracing::{debug, info, warn},
};

use strum::Display as EnumDisplay;

use crate::{
    config::ServiceConfig,
    error::RdapServerError,
    storage::{StoreOps, TxHandle},
};

pub const UPDATE: &str = "update";
pub const RELOAD: &str = "reload";

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, EnumDisplay)]
#[serde(untagged)]
pub enum Template {
    Domain {
        domain: DomainOrError,
        ids: Vec<DomainId>,
    },
    Entity {
        entity: EntityOrError,
        ids: Vec<EntityId>,
    },
    Nameserver {
        nameserver: NameserverOrError,
        ids: Vec<NameserverId>,
    },
    Autnum {
        autnum: AutnumOrError,
        ids: Vec<AutnumId>,
    },
    Network {
        network: NetworkOrError,
        ids: Vec<NetworkId>,
    },
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum DomainOrError {
    #[serde(rename = "object")]
    DomainObject(Box<Domain>),
    #[serde(rename = "error")]
    ErrorResponse(icann_rdap_common::response::Rfc9083Error),
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum EntityOrError {
    #[serde(rename = "object")]
    EntityObject(Box<Entity>),
    #[serde(rename = "error")]
    ErrorResponse(icann_rdap_common::response::Rfc9083Error),
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum NameserverOrError {
    #[serde(rename = "object")]
    NameserverObject(Box<Nameserver>),
    #[serde(rename = "error")]
    ErrorResponse(icann_rdap_common::response::Rfc9083Error),
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum AutnumOrError {
    #[serde(rename = "object")]
    AutnumObject(Box<Autnum>),
    #[serde(rename = "error")]
    ErrorResponse(icann_rdap_common::response::Rfc9083Error),
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum NetworkOrError {
    #[serde(rename = "object")]
    NetworkObject(Box<Network>),
    #[serde(rename = "error")]
    ErrorResponse(icann_rdap_common::response::Rfc9083Error),
}

#[derive(Clone, Serialize, Deserialize, Builder, Debug, PartialEq, Eq)]
pub struct DomainId {
    #[serde(rename = "ldhName")]
    pub ldh_name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(rename = "unicodeName")]
    pub unicode_name: Option<String>,
}

#[derive(Clone, Serialize, Deserialize, Builder, Debug, PartialEq, Eq)]
pub struct EntityId {
    pub handle: String,
}

#[derive(Clone, Serialize, Deserialize, Builder, Debug, PartialEq, Eq)]
pub struct NameserverId {
    #[serde(rename = "ldhName")]
    pub ldh_name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(rename = "unicodeName")]
    pub unicode_name: Option<String>,
}

#[derive(Clone, Serialize, Deserialize, Builder, Debug, PartialEq, Eq)]
pub struct AutnumId {
    #[serde(rename = "startAutnum")]
    pub start_autnum: u32,
    #[serde(rename = "endAutnum")]
    pub end_autnum: u32,
}

#[derive(Clone, Serialize, Deserialize, Builder, Debug, PartialEq, Eq)]
pub struct NetworkId {
    #[serde(rename = "networkId")]
    pub network_id: NetworkIdType,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(untagged)]
pub enum NetworkIdType {
    Cidr(IpNet),
    Range {
        #[serde(rename = "startAddress")]
        start_address: String,
        #[serde(rename = "endAddress")]
        end_address: String,
    },
}

/// Loads files from the data directory into memory.
///
/// There are 2 types of files that will be selected. Files ending with a `.json` extension
/// are considered to be JSON files holding one RDAP response each.
///
/// Files ending with a `.template` extension are a means to quickly create RDAP objects using
/// a template. Templates follow a pattern of a set of IDs paired with an RDAP object:
///
/// ```json
/// {
///   "domain":
///     {
///       "object":
///         {
///           "objectClassName":"domain",
///           "ldhName":"example"
///         }
///     },
///   "ids":
///     [
///       {"ldhName":"bar.example"},
///       {"ldhName":"foo.example"}
///     ]
/// }
/// ```
/// In this example, 2 domains will be created for "foo.example" and "bar.example" using
/// the template.
pub async fn load_data(
    config: &ServiceConfig,
    store: &dyn StoreOps,
    truncate: bool,
) -> Result<(), RdapServerError> {
    let mut json_count: usize = 0;
    let mut template_count: usize = 0;
    let mut srvhelp_count: usize = 0;
    let mut tx = if truncate {
        store.new_truncate_tx().await?
    } else {
        store.new_tx().await?
    };
    let path = PathBuf::from(&config.data_dir);
    if !path.exists() || !path.is_dir() {
        warn!(
            "Directory {} does not exist or is not a directory. Server has no content to serve.",
            path.to_string_lossy()
        );
        return Ok(());
    }

    let mut entries = tokio::fs::read_dir(path).await?;
    while let Some(entry) = entries.next_entry().await? {
        let entry_path = entry.path();
        let contents = tokio::fs::read_to_string(&entry_path).await?;
        if entry_path.extension().is_some_and(|ext| ext == "template") {
            load_rdap_template(&contents, &entry_path.to_string_lossy(), &mut tx).await?;
            template_count += 1;
        } else if entry_path.extension().is_some_and(|ext| ext == "json") {
            load_rdap(&contents, &entry_path.to_string_lossy(), &mut tx).await?;
            json_count += 1;
        } else if entry_path.extension().is_some_and(|ext| ext == "help") {
            load_srvhelp(
                &contents,
                &entry_path.to_string_lossy(),
                &entry.file_name().to_string_lossy(),
                &mut tx,
            )
            .await?;
            srvhelp_count += 1;
        }
    }

    info!("{json_count} RDAP JSON files loaded.");
    info!("{template_count} RDAP template files loaded.");
    info!("{srvhelp_count} RDAP server help files loaded.");
    if json_count == 0 && template_count == 0 && srvhelp_count == 0 {
        warn!("No data loaded. Server has no content to serve.");
    }
    tx.commit().await?;
    Ok(())
}

/// Loads the RDAP JSON files and puts them in storage.
async fn load_rdap(
    contents: &str,
    path_name: &str,
    tx: &mut Box<dyn TxHandle>,
) -> Result<(), RdapServerError> {
    debug!("loading {path_name} into storage");
    let json = serde_json::from_str::<Value>(contents);
    let Ok(value) = json else {
        return Err(RdapServerError::NonJsonFile(path_name.to_owned()));
    };
    let rdap = RdapResponse::try_from(value);
    let Ok(rdap) = rdap else {
        return Err(RdapServerError::NonRdapJsonFile(path_name.to_owned()));
    };
    match rdap {
        RdapResponse::Entity(entity) => tx.add_entity(&entity).await,
        RdapResponse::Domain(domain) => tx.add_domain(&domain).await,
        RdapResponse::Nameserver(nameserver) => tx.add_nameserver(&nameserver).await,
        RdapResponse::Autnum(autnum) => tx.add_autnum(&autnum).await,
        RdapResponse::Network(network) => tx.add_network(&network).await,
        _ => return Err(RdapServerError::NonRdapJsonFile(path_name.to_owned())),
    }?;
    Ok(())
}

/// Loads the RDAP HELP files and puts them in storage.
async fn load_srvhelp(
    contents: &str,
    path_name: &str,
    file_name: &str,
    tx: &mut Box<dyn TxHandle>,
) -> Result<(), RdapServerError> {
    debug!("loading {path_name} into storage");
    let Some(host) = file_name.strip_suffix(".help") else {
        return Err(RdapServerError::NonRdapJsonFile(path_name.to_string()));
    };
    let host = host.replace('_', ".");
    let json = serde_json::from_str::<Value>(contents);
    let Ok(value) = json else {
        return Err(RdapServerError::NonJsonFile(path_name.to_owned()));
    };
    let rdap = RdapResponse::try_from(value);
    let Ok(rdap) = rdap else {
        return Err(RdapServerError::NonRdapJsonFile(path_name.to_owned()));
    };
    match rdap {
        RdapResponse::Help(srvhelp) => tx.add_srv_help(&srvhelp, Some(&host)).await,
        _ => return Err(RdapServerError::NonRdapJsonFile(path_name.to_owned())),
    }?;
    Ok(())
}

/// Loads the template files, creates RDAP objects from the templates, and puts them
/// into storage.
async fn load_rdap_template(
    contents: &str,
    path_name: &str,
    tx: &mut Box<dyn TxHandle>,
) -> Result<(), RdapServerError> {
    debug!("processing {path_name} template");
    let json = serde_json::from_str::<Template>(contents);
    if let Ok(value) = json {
        match value {
            Template::Domain { domain, ids } => {
                for id in ids {
                    debug!("adding domain from template for {id:?}");
                    match &domain {
                        DomainOrError::DomainObject(domain) => {
                            let domain = make_domain_from_template(domain, id);
                            tx.add_domain(&domain).await?;
                        }
                        DomainOrError::ErrorResponse(error) => {
                            tx.add_domain_err(&id, error).await?;
                        }
                    };
                }
            }
            Template::Entity { entity, ids } => {
                for id in ids {
                    debug!("adding entity from template for {id:?}");
                    match &entity {
                        EntityOrError::EntityObject(entity) => {
                            let entity = make_entity_from_template(entity, id);
                            tx.add_entity(&entity).await?;
                        }
                        EntityOrError::ErrorResponse(error) => {
                            tx.add_entity_err(&id, error).await?;
                        }
                    };
                }
            }
            Template::Nameserver { nameserver, ids } => {
                for id in ids {
                    debug!("adding nameserver from template for {id:?}");
                    match &nameserver {
                        NameserverOrError::NameserverObject(nameserver) => {
                            let nameserver = make_nameserver_from_template(nameserver, id);
                            tx.add_nameserver(&nameserver).await?;
                        }
                        NameserverOrError::ErrorResponse(error) => {
                            tx.add_nameserver_err(&id, error).await?;
                        }
                    };
                }
            }
            Template::Autnum { autnum, ids } => {
                for id in ids {
                    debug!("adding autnum from template for {id:?}");
                    match &autnum {
                        AutnumOrError::AutnumObject(autnum) => {
                            let autnum = make_autnum_from_template(autnum, id);
                            tx.add_autnum(&autnum).await?;
                        }
                        AutnumOrError::ErrorResponse(error) => {
                            tx.add_autnum_err(&id, error).await?;
                        }
                    };
                }
            }
            Template::Network { network, ids } => {
                for id in ids {
                    debug!("adding network from template for {id:?}");
                    match &network {
                        NetworkOrError::NetworkObject(network) => {
                            let network = make_network_from_template(network, id)?;
                            tx.add_network(&network).await?;
                        }
                        NetworkOrError::ErrorResponse(error) => {
                            tx.add_network_err(&id, error).await?;
                        }
                    };
                }
            }
        };
    } else {
        return Err(RdapServerError::NonJsonFile(path_name.to_owned()));
    }
    Ok(())
}

pub(crate) async fn reload_data(
    store: Box<dyn StoreOps>,
    config: ServiceConfig,
) -> Result<(), RdapServerError> {
    let update_path = PathBuf::from(&config.data_dir);
    let update_path = update_path.join(UPDATE);
    let reload_path = PathBuf::from(&config.data_dir);
    let reload_path = reload_path.join(RELOAD);
    let mut last_time = SystemTime::now();
    loop {
        sleep(Duration::from_millis(1000)).await;
        let update_meta = tokio::fs::metadata(&update_path).await;
        if let Ok(update_meta) = update_meta {
            let modified = update_meta.modified()?;
            if modified > last_time {
                last_time = modified;
                info!("Data being updated.");
                load_data(&config, &*store, false).await?;
            }
        };
        let reload_meta = tokio::fs::metadata(&reload_path).await;
        if let Ok(reload_meta) = reload_meta {
            let modified = reload_meta.modified()?;
            if modified > last_time {
                last_time = modified;
                info!("Data being reloaded.");
                load_data(&config, &*store, true).await?;
            }
        };
    }
}

pub async fn trigger_reload(data_dir: &str) -> Result<(), RdapServerError> {
    let reload_path = PathBuf::from(&data_dir);
    let reload_path = reload_path.join(RELOAD);
    tokio::fs::File::create(reload_path).await?;
    Ok(())
}

pub async fn trigger_update(data_dir: &str) -> Result<(), RdapServerError> {
    let update_path = PathBuf::from(&data_dir);
    let update_path = update_path.join(UPDATE);
    tokio::fs::File::create(update_path).await?;
    Ok(())
}

fn change_self_link<T: GetSelfLink + SelfLink>(mut object: T, segment: &str, id: &str) -> T {
    if let Some(self_link) = object.self_link() {
        if let Some(self_href) = &self_link.href {
            if let Some(self_href_split) = self_href.rsplit_once(segment) {
                let mut new_self_link = self_link.clone();
                new_self_link.href = Some(format!("{}{segment}/{}", self_href_split.0, id));
                object = object.with_self_link(new_self_link);
            } else {
                warn!("Unable to rewrite self link for {segment} {}", id);
            }
        } else {
            warn!("Unable to use self link because it has not href")
        }
    } else {
        warn!("No self link for {segment} {}", id);
    }
    object
}

fn make_domain_from_template(domain: &Domain, id: DomainId) -> Domain {
    let mut domain = domain.clone();
    domain = change_self_link(domain, "domain", &id.ldh_name);
    domain.ldh_name = Some(id.ldh_name);
    if let Some(unicode_name) = id.unicode_name {
        domain.unicode_name = Some(unicode_name);
    };
    domain
}

fn make_entity_from_template(entity: &Entity, id: EntityId) -> Entity {
    let mut entity = entity.clone();
    entity = change_self_link(entity, "entity", &id.handle);
    entity.object_common.handle = Some(id.handle.into());
    entity
}

fn make_nameserver_from_template(nameserver: &Nameserver, id: NameserverId) -> Nameserver {
    let mut nameserver = nameserver.clone();
    nameserver = change_self_link(nameserver, "nameserver", &id.ldh_name);
    nameserver.ldh_name = Some(id.ldh_name);
    if let Some(unicode_name) = id.unicode_name {
        nameserver.unicode_name = Some(unicode_name);
    };
    nameserver
}

fn make_autnum_from_template(autnum: &Autnum, id: AutnumId) -> Autnum {
    let mut autnum = autnum.clone();
    autnum = change_self_link(autnum, "autnum", &id.start_autnum.to_string());
    autnum.start_autnum = Some(Numberish::<u32>::from(id.start_autnum));
    autnum.end_autnum = Some(Numberish::<u32>::from(id.end_autnum));
    autnum
}

fn make_network_from_template(
    network: &Network,
    id: NetworkId,
) -> Result<Network, RdapServerError> {
    let mut network = network.clone();
    match id.network_id {
        NetworkIdType::Cidr(cidr) => match cidr {
            IpNet::V4(v4) => {
                network.start_address = Some(v4.network().to_string());
                network.end_address = Some(v4.broadcast().to_string());
                network.ip_version = Some("v4".to_string().into());
                network.cidr0_cidrs = Some(vec![
                    Cidr0Cidr::v4()
                        .prefix(v4.network().to_string())
                        .length(v4.prefix_len())
                        .build(),
                ])
            }
            IpNet::V6(v6) => {
                network.start_address = Some(v6.network().to_string());
                network.end_address = Some(v6.broadcast().to_string());
                network.ip_version = Some("v6".to_string().into());
                network.cidr0_cidrs = Some(vec![
                    Cidr0Cidr::v6()
                        .prefix(v6.network().to_string())
                        .length(v6.prefix_len())
                        .build(),
                ]);
            }
        },
        NetworkIdType::Range {
            start_address,
            end_address,
        } => {
            let addr: IpAddr = start_address.parse()?;
            if addr.is_ipv4() {
                network.ip_version = Some("v4".to_string().into());
                network.cidr0_cidrs = Some(
                    Ipv4Subnets::new(start_address.parse()?, end_address.parse()?, 0)
                        .map(|net| {
                            Cidr0Cidr::v4()
                                .prefix(net.network().to_string())
                                .length(net.prefix_len())
                                .build()
                        })
                        .collect::<Vec<Cidr0Cidr>>(),
                );
            } else {
                network.ip_version = Some("v6".to_string().into());
                network.cidr0_cidrs = Some(
                    Ipv6Subnets::new(start_address.parse()?, end_address.parse()?, 0)
                        .map(|net| {
                            Cidr0Cidr::v6()
                                .prefix(net.network().to_string())
                                .length(net.prefix_len())
                                .build()
                        })
                        .collect::<Vec<Cidr0Cidr>>(),
                );
            }
            network.start_address = Some(start_address);
            network.end_address = Some(end_address);
        }
    }
    let first_cidr = network
        .cidr0_cidrs
        .as_ref()
        .expect("cidrs should be on network")
        .first()
        .map(|cidr| cidr.to_string())
        .expect("cidrs on network are empty");
    network = change_self_link(network, "ip", &first_cidr);
    Ok(network)
}

#[cfg(test)]
mod tests {

    use icann_rdap_common::{
        prelude::Stringish,
        response::{Domain, Link},
    };

    use super::*;

    #[test]
    fn test_template_domain_serialize() {
        // GIVEN
        let template = Template::Domain {
            domain: DomainOrError::DomainObject(Box::new(
                Domain::response_obj().ldh_name("foo.example").build(),
            )),
            ids: vec![DomainId::builder().ldh_name("bar.example").build()],
        };

        // WHEN
        let actual = serde_json::to_string(&template).expect("serializing template");

        // THEN
        assert_eq!(
            actual,
            r#"{"domain":{"object":{"rdapConformance":["rdap_level_0"],"objectClassName":"domain","ldhName":"foo.example"}},"ids":[{"ldhName":"bar.example"}]}"#
        );
    }

    #[test]
    fn test_template_domain_text_deserialize() {
        // GIVEN
        let json_text = r#"{"domain":{"object":{"rdapConformance":["rdap_level_0"],"objectClassName":"domain","ldhName":"foo.example"}},"ids":[{"ldhName":"bar.example"}]}"#;

        // WHEN
        let actual: Template = serde_json::from_str(json_text).expect("deserializing template");

        // THEN
        let expected = Template::Domain {
            domain: DomainOrError::DomainObject(Box::new(
                Domain::response_obj().ldh_name("foo.example").build(),
            )),
            ids: vec![DomainId::builder().ldh_name("bar.example").build()],
        };
        assert_eq!(actual, expected);
    }

    #[test]
    fn test_template_network_with_cidr_serialize() {
        // GIVEN
        let template = Template::Network {
            network: NetworkOrError::NetworkObject(Box::new(
                Network::response_obj()
                    .cidr("10.0.0.0/24")
                    .build()
                    .expect("cidr parsing"),
            )),
            ids: vec![
                NetworkId::builder()
                    .network_id(NetworkIdType::Cidr(
                        "10.0.0.0/24".parse().expect("ipnet parsing"),
                    ))
                    .build(),
            ],
        };

        // WHEN
        let actual = serde_json::to_string(&template).expect("serializing template");
        let actual: Template = serde_json::from_str(&actual).expect("deserialize network template");

        // THEN
        let expected = r#"
        {
            "network":{
                "object":{
                    "rdapConformance":["cidr0","rdap_level_0"],
                    "objectClassName":"ip network",
                    "startAddress":"10.0.0.0",
                    "endAddress":"10.0.0.255",
                    "ipVersion":"v4",
                    "cidr0_cidrs":[{"v4prefix":"10.0.0.0","length":24}]
                }
            },
            "ids":[
                {"networkId":"10.0.0.0/24"}
            ]
        }"#;
        let expected: Template = serde_json::from_str(expected).expect("deserialize expected");
        assert_eq!(actual, expected);
    }

    #[test]
    fn test_template_network_with_start_and_end_serialize() {
        // GIVEN
        let template = Template::Network {
            network: NetworkOrError::NetworkObject(Box::new(
                Network::response_obj()
                    .cidr("10.0.0.0/24")
                    .build()
                    .expect("cidr parsing"),
            )),
            ids: vec![
                NetworkId::builder()
                    .network_id(NetworkIdType::Range {
                        start_address: "10.0.0.0".to_string(),
                        end_address: "10.0.0.255".to_string(),
                    })
                    .build(),
            ],
        };

        // WHEN
        let actual = serde_json::to_string(&template).expect("serializing template");
        let actual: Template = serde_json::from_str(&actual).expect("deserialize network template");

        // THEN
        let expected = r#"
        {
            "network":{
                "object":{
                    "rdapConformance":["cidr0","rdap_level_0"],
                    "objectClassName":"ip network",
                    "startAddress":"10.0.0.0",
                    "endAddress":"10.0.0.255",
                    "ipVersion":"v4",
                    "cidr0_cidrs":[
                        {"v4prefix":"10.0.0.0","length":24}
                    ]
                }
            },
            "ids":[
                {"networkId":
                    {"startAddress":"10.0.0.0","endAddress":"10.0.0.255"}
                }
            ]
        }"#;
        let expected: Template = serde_json::from_str(expected).expect("deserialize expected");
        assert_eq!(actual, expected);
    }

    #[test]
    fn test_template_network_with_cidr_text_deserialize() {
        // GIVEN
        let text = r#"
        {
            "network":{
                "object":{
                    "rdapConformance":["cidr0","rdap_level_0"],
                    "objectClassName":"ip network",
                    "startAddress":"10.0.0.0",
                    "endAddress":"10.0.0.255",
                    "ipVersion":"v4",
                    "cidr0_cidrs":[{"v4prefix":"10.0.0.0","length":24}]
                }
            },
            "ids":[
                {"networkId":"10.0.0.0/24"}
            ]
        }"#;

        // WHEN
        let actual: Template = serde_json::from_str(text).expect("deserialize network template");

        // THEN
        let expected = Template::Network {
            network: NetworkOrError::NetworkObject(Box::new(
                Network::response_obj()
                    .cidr("10.0.0.0/24")
                    .build()
                    .expect("cidr parsing"),
            )),
            ids: vec![
                NetworkId::builder()
                    .network_id(NetworkIdType::Cidr(
                        "10.0.0.0/24".parse().expect("ipnet parsing"),
                    ))
                    .build(),
            ],
        };
        assert_eq!(actual, expected);
    }

    #[test]
    fn test_template_network_with_range_text_deserialize() {
        // GIVEN
        let text = r#"
        {
            "network":{
                "object":{
                    "rdapConformance":["cidr0","rdap_level_0"],
                    "objectClassName":"ip network",
                    "startAddress":"10.0.0.0",
                    "endAddress":"10.0.0.255",
                    "ipVersion":"v4",
                    "cidr0_cidrs":[
                        {"v4prefix":"10.0.0.0","length":24}
                    ]
                }
            },
            "ids":[
                {"networkId":
                    {"startAddress":"10.0.0.0","endAddress":"10.0.0.255"}
                }
            ]
        }"#;

        // WHEN
        let actual: Template = serde_json::from_str(text).expect("deserialize network template");

        // THEN
        let expected = Template::Network {
            network: NetworkOrError::NetworkObject(Box::new(
                Network::response_obj()
                    .cidr("10.0.0.0/24")
                    .build()
                    .expect("cidr parsing"),
            )),
            ids: vec![
                NetworkId::builder()
                    .network_id(NetworkIdType::Range {
                        start_address: "10.0.0.0".to_string(),
                        end_address: "10.0.0.255".to_string(),
                    })
                    .build(),
            ],
        };
        assert_eq!(actual, expected);
    }

    #[test]
    fn test_make_domain_with_domain_and_id() {
        // GIVEN
        let domain = Domain::builder()
            .ldh_name("foo.example")
            .link(
                Link::builder()
                    .rel("self")
                    .href("http://reg.example/domain/foo.example")
                    .value("http://reg.example/domain/foo.example")
                    .build(),
            )
            .build();
        let id = DomainId::builder().ldh_name("bar.example").build();

        // WHEN
        let actual = make_domain_from_template(&domain, id);

        // THEN
        assert_eq!(
            actual.ldh_name.as_ref().expect("no ldhname on domain"),
            "bar.example"
        );
        let self_link = actual.self_link().expect("self link messing");
        assert_eq!(
            self_link.href.as_ref().expect("link has no href"),
            "http://reg.example/domain/bar.example"
        );
    }

    #[test]
    fn test_make_entity_with_entity_and_id() {
        // GIVEN
        let entity = Entity::builder()
            .handle("foo")
            .link(
                Link::builder()
                    .rel("self")
                    .href("http://reg.example/entity/foo")
                    .value("http://reg.example/entity/foo")
                    .build(),
            )
            .build();
        let id = EntityId::builder().handle("bar").build();

        // WHEN
        let actual = make_entity_from_template(&entity, id);

        // THEN
        assert_eq!(
            actual
                .object_common
                .handle
                .as_ref()
                .expect("no handle on entity"),
            &Stringish::from("bar")
        );
        let self_link = actual.self_link().expect("self link messing");
        assert_eq!(
            self_link.href.as_ref().expect("link has no href"),
            "http://reg.example/entity/bar"
        );
    }

    #[test]
    fn test_make_ns_with_nameserver_and_id() {
        // GIVEN
        let nameserver = Nameserver::builder()
            .ldh_name("ns.foo.example")
            .link(
                Link::builder()
                    .rel("self")
                    .href("http://reg.example/nameserver/ns.foo.example")
                    .value("http://reg.example/nameserver/ns.foo.example")
                    .build(),
            )
            .build()
            .expect("creation of nameserver");
        let id = NameserverId::builder().ldh_name("ns.bar.example").build();

        // WHEN
        let actual = make_nameserver_from_template(&nameserver, id);

        // THEN
        assert_eq!(
            actual.ldh_name.as_ref().expect("no ldhname on nameserver"),
            "ns.bar.example"
        );
        let self_link = actual.self_link().expect("self link messing");
        assert_eq!(
            self_link.href.as_ref().expect("link has no href"),
            "http://reg.example/nameserver/ns.bar.example"
        );
    }

    #[test]
    fn test_make_autnum_with_autnum_and_id() {
        // GIVEN
        let autnum = Autnum::builder()
            .autnum_range(700..710)
            .link(
                Link::builder()
                    .rel("self")
                    .href("http://reg.example/autnum/700")
                    .value("http://reg.example/autnum/700")
                    .build(),
            )
            .build();
        let id = AutnumId::builder()
            .start_autnum(900)
            .end_autnum(999)
            .build();

        // WHEN
        let actual = make_autnum_from_template(&autnum, id);

        // THEN
        assert_eq!(
            *actual.start_autnum.as_ref().expect("no startnum on autnum"),
            Numberish::<u32>::from(900)
        );
        assert_eq!(
            *actual.end_autnum.as_ref().expect("no end on autnum"),
            Numberish::<u32>::from(999)
        );
        let self_link = actual.self_link().expect("self link messing");
        assert_eq!(
            self_link.href.as_ref().expect("link has href"),
            "http://reg.example/autnum/900"
        );
    }

    #[test]
    fn test_make_network_with_id_and_range() {
        // GIVEN
        let network = Network::builder()
            .cidr("10.0.0.0/24")
            .link(
                Link::builder()
                    .rel("self")
                    .href("http://reg.example/ip/10.0.0.0/24")
                    .value("http://reg.example/ip/10.0.0.0/24")
                    .build(),
            )
            .build()
            .expect("creating test network");
        let id = NetworkId::builder()
            .network_id(NetworkIdType::Range {
                start_address: "11.0.0.0".to_string(),
                end_address: "11.0.0.255".to_string(),
            })
            .build();

        // WHEN
        let actual = make_network_from_template(&network, id).expect("unable to make network");

        // THEN
        assert_eq!(
            actual
                .start_address
                .as_ref()
                .expect("no start address on network"),
            "11.0.0.0"
        );
        assert_eq!(
            actual
                .end_address
                .as_ref()
                .expect("no end address on network"),
            "11.0.0.255"
        );
        let cidr0 = actual.cidr0_cidrs.as_ref().expect("no cidr0");
        let v4cidr = cidr0.first().expect("cidr0 is empty");
        assert_eq!(v4cidr.prefix(), Some("11.0.0.0".to_string()));
        assert_eq!(v4cidr.length(), Some(24));
        let self_link = actual.self_link().expect("self link messing");
        assert_eq!(
            self_link.href.as_ref().expect("link has no href"),
            "http://reg.example/ip/11.0.0.0/24"
        );
    }

    #[test]
    fn test_make_network_with_cidr_and_id() {
        // GIVEN
        let network = Network::builder()
            .cidr("10.0.0.0/24")
            .link(
                Link::builder()
                    .rel("self")
                    .href("http://reg.example/ip/10.0.0.0/24")
                    .value("http://reg.example/ip/10.0.0.0/24")
                    .build(),
            )
            .build()
            .expect("creating test network");
        let id = NetworkId::builder()
            .network_id(NetworkIdType::Cidr(IpNet::V4(
                "11.0.0.0/24".parse().expect("bad cidr"),
            )))
            .build();

        // WHEN
        let actual = make_network_from_template(&network, id).expect("unable to make network");

        // THEN
        assert_eq!(
            actual
                .start_address
                .as_ref()
                .expect("no start address on network"),
            "11.0.0.0"
        );
        assert_eq!(
            actual
                .end_address
                .as_ref()
                .expect("no end address on network"),
            "11.0.0.255"
        );
        let cidr0 = actual.cidr0_cidrs.as_ref().expect("no cidr0");
        let v4cidr = cidr0.first().expect("cidr0 is empty");
        assert_eq!(v4cidr.prefix(), Some("11.0.0.0".to_string()));
        assert_eq!(v4cidr.length(), Some(24));
        let self_link = actual.self_link().expect("self link messing");
        assert_eq!(
            self_link.href.as_ref().expect("link has no href"),
            "http://reg.example/ip/11.0.0.0/24"
        );
    }
}
