mod ha_cluster;
mod host_binding;
mod http;
mod load_balancer;
pub mod openshift;
mod router;
mod tftp;
pub use ha_cluster::*;
pub use load_balancer::*;
pub use router::*;
mod network;
pub use host_binding::*;
pub use http::*;
pub use network::*;
use serde::Serialize;
pub use tftp::*;

use std::net::IpAddr;

pub trait Topology {
    fn name(&self) -> &str;
}

pub type IpAddress = IpAddr;

#[derive(Debug, Clone)]
pub enum Url {
    LocalFolder(String),
    Url(url::Url),
}

impl Serialize for Url {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        match self {
            Url::LocalFolder(path) => serializer.serialize_str(path),
            Url::Url(url) => serializer.serialize_str(&url.as_str()),
        }
    }
}

impl std::fmt::Display for Url {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Url::LocalFolder(path) => write!(f, "{}", path),
            Url::Url(url) => write!(f, "{}", url),
        }
    }
}

/// Represents a logical member of a cluster that provides one or more services.
///
/// A LogicalHost can represent various roles within the infrastructure, such as:
/// - A firewall appliance hosting DHCP, DNS, PXE, and load balancer services
/// - A Kubernetes worker node
/// - A combined Kubernetes worker and Ceph storage node
/// - A control plane node
///
/// This abstraction focuses on the logical role and services, independent of the physical hardware.
#[derive(Debug, Clone, Serialize)]
pub struct LogicalHost {
    /// The IP address of this logical host.
    pub ip: IpAddress,
    /// The name of this logical host.
    pub name: String,
}

impl LogicalHost {
    /// Creates a list of `LogicalHost` instances.
    ///
    /// # Arguments
    ///
    /// * `number_hosts` - The number of logical hosts to create.
    /// * `start_ip` - The starting IP address. Each subsequent host's IP will be incremented.
    /// * `hostname_prefix` - The prefix for the host names. Host names will be in the form `prefix<index>`.
    ///
    /// # Returns
    ///
    /// A `Vec<LogicalHost>` containing the specified number of logical hosts, each with a unique IP and name.
    ///
    /// # Panics
    ///
    /// This function will panic if adding `number_hosts` to `start_ip` exceeds the valid range of IP addresses.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::str::FromStr;
    /// use harmony::topology::{IpAddress, LogicalHost};
    ///
    /// let start_ip = IpAddress::from_str("192.168.0.20").unwrap();
    /// let hosts = LogicalHost::create_hosts(3, start_ip, "worker");
    ///
    /// assert_eq!(hosts.len(), 3);
    /// assert_eq!(hosts[0].ip, IpAddress::from_str("192.168.0.20").unwrap());
    /// assert_eq!(hosts[0].name, "worker0");
    /// assert_eq!(hosts[1].ip, IpAddress::from_str("192.168.0.21").unwrap());
    /// assert_eq!(hosts[1].name, "worker1");
    /// assert_eq!(hosts[2].ip, IpAddress::from_str("192.168.0.22").unwrap());
    /// assert_eq!(hosts[2].name, "worker2");
    /// ```
    pub fn create_hosts(
        number_hosts: u32,
        start_ip: IpAddress,
        hostname_prefix: &str,
    ) -> Vec<LogicalHost> {
        let mut hosts = Vec::with_capacity(number_hosts.try_into().unwrap());
        for i in 0..number_hosts {
            let new_ip = increment_ip(start_ip, i).expect("IP address overflow");
            let name = format!("{}{}", hostname_prefix, i);
            hosts.push(LogicalHost { ip: new_ip, name });
        }
        hosts
    }
}

/// Increments an IP address by a given value.
///
/// # Arguments
///
/// * `ip` - The starting IP address.
/// * `increment` - The amount to add to the IP address.
///
/// # Returns
///
/// A new `IpAddress` that is the result of incrementing the original by `increment`.
///
/// # Panics
///
/// This function panics if the resulting IP address exceeds the valid range.
fn increment_ip(ip: IpAddress, increment: u32) -> Option<IpAddress> {
    match ip {
        IpAddress::V4(ipv4) => {
            let new_ip = u32::from(ipv4) + increment;
            Some(IpAddress::V4(new_ip.into()))
        }
        IpAddress::V6(_) => {
            todo!("Ipv6 not supported yet")
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json;

    #[test]
    fn test_serialize_local_folder() {
        let url = Url::LocalFolder("path/to/folder".to_string());
        let serialized = serde_json::to_string(&url).unwrap();
        assert_eq!(serialized, "\"path/to/folder\"");
    }

    #[test]
    fn test_serialize_url() {
        let url = Url::Url(url::Url::parse("https://example.com").unwrap());
        let serialized = serde_json::to_string(&url).unwrap();
        assert_eq!(serialized, "\"https://example.com/\"");
    }
}
