use std::sync::Arc;

use derive_new::new;
use harmony_types::net::MacAddress;
use serde::{Serialize, Serializer, ser::SerializeStruct};
use serde_value::Value;

pub type HostGroup = Vec<PhysicalHost>;
pub type SwitchGroup = Vec<Switch>;
pub type FirewallGroup = Vec<PhysicalHost>;

#[derive(Debug, Clone)]
pub struct PhysicalHost {
    pub category: HostCategory,
    pub network: Vec<NetworkInterface>,
    pub management: Arc<dyn ManagementInterface>,
    pub storage: Vec<Storage>,
    pub labels: Vec<Label>,
    pub memory_size: Option<u64>,
    pub cpu_count: Option<u64>,
}

impl PhysicalHost {
    pub fn empty(category: HostCategory) -> Self {
        Self {
            category,
            network: vec![],
            storage: vec![],
            labels: vec![],
            management: Arc::new(ManualManagementInterface {}),
            memory_size: None,
            cpu_count: None,
        }
    }

    pub fn cluster_mac(&self) -> MacAddress {
        self.network
            .get(0)
            .expect("Cluster physical host should have a network interface")
            .mac_address
            .clone()
    }

    pub fn cpu(mut self, cpu_count: Option<u64>) -> Self {
        self.cpu_count = cpu_count;
        self
    }

    pub fn memory_size(mut self, memory_size: Option<u64>) -> Self {
        self.memory_size = memory_size;
        self
    }

    pub fn storage(
        mut self,
        connection: StorageConnectionType,
        kind: StorageKind,
        size: u64,
        serial: String,
    ) -> Self {
        self.storage.push(Storage {
            connection,
            kind,
            size,
            serial,
        });
        self
    }

    pub fn mac_address(mut self, mac_address: MacAddress) -> Self {
        self.network.push(NetworkInterface {
            name: None,
            mac_address,
            speed: None,
        });
        self
    }

    pub fn label(mut self, name: String, value: String) -> Self {
        self.labels.push(Label { name, value });
        self
    }

    pub fn management(mut self, management: Arc<dyn ManagementInterface>) -> Self {
        self.management = management;
        self
    }
}

// Custom Serialize implementation for PhysicalHost
impl Serialize for PhysicalHost {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // Determine the number of fields
        let mut num_fields = 5; // category, network, storage, labels, management
        if self.memory_size.is_some() {
            num_fields += 1;
        }
        if self.cpu_count.is_some() {
            num_fields += 1;
        }

        // Create a serialization structure
        let mut state = serializer.serialize_struct("PhysicalHost", num_fields)?;

        // Serialize the standard fields
        state.serialize_field("category", &self.category)?;
        state.serialize_field("network", &self.network)?;
        state.serialize_field("storage", &self.storage)?;
        state.serialize_field("labels", &self.labels)?;

        // Serialize optional fields
        if let Some(memory) = self.memory_size {
            state.serialize_field("memory_size", &memory)?;
        }
        if let Some(cpu) = self.cpu_count {
            state.serialize_field("cpu_count", &cpu)?;
        }

        let mgmt_data = self.management.serialize_management();
        // pub management: Arc<dyn ManagementInterface>,

        // Handle management interface - either as a field or flattened
        state.serialize_field("management", &mgmt_data)?;

        state.end()
    }
}

#[derive(new, Serialize)]
pub struct ManualManagementInterface;

impl ManagementInterface for ManualManagementInterface {
    fn boot_to_pxe(&self) {
        todo!()
    }

    fn get_supported_protocol_names(&self) -> String {
        todo!()
    }
}

pub trait ManagementInterface: Send + Sync + SerializableManagement {
    fn boot_to_pxe(&self);
    fn get_supported_protocol_names(&self) -> String;
}

impl std::fmt::Debug for dyn ManagementInterface {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_fmt(format_args!(
            "ManagementInterface protocols : {}",
            self.get_supported_protocol_names(),
        ))
    }
}

// Define a trait for serializing management interfaces
pub trait SerializableManagement {
    fn serialize_management(&self) -> Value;
}

// Provide a blanket implementation for all types that implement both ManagementInterface and Serialize
impl<T> SerializableManagement for T
where
    T: ManagementInterface + Serialize,
{
    fn serialize_management(&self) -> Value {
        serde_value::to_value(self).expect("ManagementInterface should serialize successfully")
    }
}

#[derive(Debug, Clone, Serialize)]
pub enum HostCategory {
    Server,
    Firewall,
    Switch,
}

#[derive(Debug, new, Clone, Serialize)]
pub struct NetworkInterface {
    pub name: Option<String>,
    pub mac_address: MacAddress,
    pub speed: Option<u64>,
}

#[cfg(test)]
use harmony_macros::mac_address;
#[cfg(test)]
impl NetworkInterface {
    pub fn dummy() -> Self {
        Self {
            name: Some(String::new()),
            mac_address: mac_address!("00:00:00:00:00:00"),
            speed: Some(0),
        }
    }
}

#[derive(Debug, new, Clone, Serialize)]
pub enum StorageConnectionType {
    Sata3g,
    Sata6g,
    Sas6g,
    Sas12g,
    PCIE,
}
#[derive(Debug, Clone, Serialize)]
pub enum StorageKind {
    SSD,
    NVME,
    HDD,
}
#[derive(Debug, new, Clone, Serialize)]
pub struct Storage {
    pub connection: StorageConnectionType,
    pub kind: StorageKind,
    pub size: u64,
    pub serial: String,
}

#[cfg(test)]
impl Storage {
    pub fn dummy() -> Self {
        Self {
            connection: StorageConnectionType::Sata3g,
            kind: StorageKind::SSD,
            size: 0,
            serial: String::new(),
        }
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct Switch {
    _interface: Vec<NetworkInterface>,
    _management_interface: NetworkInterface,
}

#[derive(Debug, new, Clone, Serialize)]
pub struct Label {
    pub name: String,
    pub value: String,
}

pub type Address = String;

#[derive(new, Debug, Serialize)]
pub struct Location {
    pub address: Address,
    pub name: String,
}

impl Location {
    pub fn test_building() -> Location {
        Self {
            address: String::new(),
            name: String::new(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde::{Deserialize, Serialize};
    use std::sync::Arc;

    // Mock implementation of ManagementInterface
    #[derive(Debug, Clone, Serialize, Deserialize)]
    struct MockHPIlo {
        ip: String,
        username: String,
        password: String,
        firmware_version: String,
    }

    impl ManagementInterface for MockHPIlo {
        fn boot_to_pxe(&self) {}

        fn get_supported_protocol_names(&self) -> String {
            String::new()
        }
    }

    // Another mock implementation
    #[derive(Debug, Clone, Serialize, Deserialize)]
    struct MockDellIdrac {
        hostname: String,
        port: u16,
        api_token: String,
    }

    impl ManagementInterface for MockDellIdrac {
        fn boot_to_pxe(&self) {}

        fn get_supported_protocol_names(&self) -> String {
            String::new()
        }
    }

    #[test]
    fn test_serialize_physical_host_with_hp_ilo() {
        // Create a PhysicalHost with HP iLO management
        let host = PhysicalHost {
            category: HostCategory::Server,
            network: vec![NetworkInterface::dummy()],
            management: Arc::new(MockHPIlo {
                ip: "192.168.1.100".to_string(),
                username: "admin".to_string(),
                password: "password123".to_string(),
                firmware_version: "2.5.0".to_string(),
            }),
            storage: vec![Storage::dummy()],
            labels: vec![Label::new("datacenter".to_string(), "us-east".to_string())],
            memory_size: Some(64_000_000),
            cpu_count: Some(16),
        };

        // Serialize to JSON
        let json = serde_json::to_string(&host).expect("Failed to serialize host");

        // Check that the serialized JSON contains the HP iLO details
        assert!(json.contains("192.168.1.100"));
        assert!(json.contains("admin"));
        assert!(json.contains("password123"));
        assert!(json.contains("firmware_version"));
        assert!(json.contains("2.5.0"));

        // Parse back to verify structure (not the exact management interface)
        let parsed: serde_json::Value = serde_json::from_str(&json).expect("Failed to parse JSON");

        // Verify basic structure
        assert_eq!(parsed["cpu_count"], 16);
        assert_eq!(parsed["memory_size"], 64_000_000);
        assert_eq!(parsed["network"][0]["name"], "");
    }

    #[test]
    fn test_serialize_physical_host_with_dell_idrac() {
        // Create a PhysicalHost with Dell iDRAC management
        let host = PhysicalHost {
            category: HostCategory::Server,
            network: vec![NetworkInterface::dummy()],
            management: Arc::new(MockDellIdrac {
                hostname: "idrac-server01".to_string(),
                port: 443,
                api_token: "abcdef123456".to_string(),
            }),
            storage: vec![Storage::dummy()],
            labels: vec![Label::new("env".to_string(), "production".to_string())],
            memory_size: Some(128_000_000),
            cpu_count: Some(32),
        };

        // Serialize to JSON
        let json = serde_json::to_string(&host).expect("Failed to serialize host");

        // Check that the serialized JSON contains the Dell iDRAC details
        assert!(json.contains("idrac-server01"));
        assert!(json.contains("443"));
        assert!(json.contains("abcdef123456"));

        // Parse back to verify structure
        let parsed: serde_json::Value = serde_json::from_str(&json).expect("Failed to parse JSON");

        // Verify basic structure
        assert_eq!(parsed["cpu_count"], 32);
        assert_eq!(parsed["memory_size"], 128_000_000);
        assert_eq!(parsed["storage"][0]["path"], serde_json::Value::Null);
    }

    #[test]
    fn test_different_management_implementations_produce_valid_json() {
        // Create hosts with different management implementations
        let host1 = PhysicalHost {
            category: HostCategory::Server,
            network: vec![],
            management: Arc::new(MockHPIlo {
                ip: "10.0.0.1".to_string(),
                username: "root".to_string(),
                password: "secret".to_string(),
                firmware_version: "3.0.0".to_string(),
            }),
            storage: vec![],
            labels: vec![],
            memory_size: None,
            cpu_count: None,
        };

        let host2 = PhysicalHost {
            category: HostCategory::Server,
            network: vec![],
            management: Arc::new(MockDellIdrac {
                hostname: "server02-idrac".to_string(),
                port: 8443,
                api_token: "token123".to_string(),
            }),
            storage: vec![],
            labels: vec![],
            memory_size: None,
            cpu_count: None,
        };

        // Both should serialize successfully
        let json1 = serde_json::to_string(&host1).expect("Failed to serialize host1");
        let json2 = serde_json::to_string(&host2).expect("Failed to serialize host2");

        // Both JSONs should be valid and parseable
        let _: serde_json::Value = serde_json::from_str(&json1).expect("Invalid JSON for host1");
        let _: serde_json::Value = serde_json::from_str(&json2).expect("Invalid JSON for host2");

        // The JSONs should be different because they contain different management interfaces
        assert_ne!(json1, json2);
    }
}
