use crate::libvirt_lib_structures::XMLUuid;

/// VirtWeb specific metadata
#[derive(serde::Serialize, serde::Deserialize, Default, Debug, Clone)]
#[serde(rename = "virtweb", default)]
pub struct DomainMetadataVirtWebXML {
    #[serde(rename = "@xmlns:virtweb", default)]
    pub ns: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub group: Option<String>,
}

/// Domain metadata
#[derive(serde::Serialize, serde::Deserialize, Default, Debug, Clone)]
#[serde(rename = "metadata")]
pub struct DomainMetadataXML {
    #[serde(rename = "virtweb:metadata", default)]
    pub virtweb: DomainMetadataVirtWebXML,
}

/// OS information
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "os")]
pub struct OSXML {
    #[serde(rename = "@firmware", default)]
    pub firmware: String,
    pub r#type: OSTypeXML,
    pub loader: Option<OSLoaderXML>,
    pub smbios: Option<OSSMBiosXML>,
}

/// OS Type information
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "os")]
pub struct OSTypeXML {
    #[serde(rename = "@arch")]
    pub arch: String,
    #[serde(rename = "@machine")]
    pub machine: String,
    #[serde(rename = "$value")]
    pub body: String,
}

/// OS Loader information
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "loader")]
pub struct OSLoaderXML {
    #[serde(rename = "@secure")]
    pub secure: String,
}

/// SMBIOS System information
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "smbios")]
pub struct OSSMBiosXML {
    #[serde(rename = "@mode")]
    pub mode: String,
}

/// Hypervisor features
#[derive(serde::Serialize, serde::Deserialize, Clone, Default, Debug)]
#[serde(rename = "features")]
pub struct FeaturesXML {
    pub acpi: ACPIXML,
}

/// ACPI feature
#[derive(serde::Serialize, serde::Deserialize, Clone, Default, Debug)]
#[serde(rename = "acpi")]
pub struct ACPIXML {}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "mac")]
pub struct NetMacAddress {
    #[serde(rename = "@address")]
    pub address: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "source")]
pub struct NetIntSourceXML {
    #[serde(rename = "@network")]
    pub network: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "model")]
pub struct NetIntModelXML {
    #[serde(rename = "@type")]
    pub r#type: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "filterref")]
pub struct NetIntFilterParameterXML {
    #[serde(rename = "@name")]
    pub name: String,
    #[serde(rename = "@value")]
    pub value: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "filterref")]
pub struct NetIntfilterRefXML {
    #[serde(rename = "@filter")]
    pub filter: String,
    #[serde(rename = "parameter", default)]
    pub parameters: Vec<NetIntFilterParameterXML>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "interface")]
pub struct DomainNetInterfaceXML {
    #[serde(rename = "@type")]
    pub r#type: String,
    pub mac: NetMacAddress,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub source: Option<NetIntSourceXML>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub model: Option<NetIntModelXML>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub filterref: Option<NetIntfilterRefXML>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "input")]
pub struct DomainInputXML {
    #[serde(rename = "@type")]
    pub r#type: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "backend")]
pub struct TPMBackendXML {
    #[serde(rename = "@type")]
    pub r#type: String,

    #[serde(rename = "@version")]
    pub r#version: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "tpm")]
pub struct TPMDeviceXML {
    #[serde(rename = "@model")]
    pub model: String,
    pub backend: TPMBackendXML,
}

/// Devices information
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "devices")]
pub struct DevicesXML {
    /// Graphics (used for VNC)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub graphics: Option<GraphicsXML>,

    /// Graphics (used for VNC)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub video: Option<VideoXML>,

    /// Disks (used for storage)
    #[serde(default, rename = "disk", skip_serializing_if = "Vec::is_empty")]
    pub disks: Vec<DiskXML>,

    /// Networks cards
    #[serde(default, rename = "interface", skip_serializing_if = "Vec::is_empty")]
    pub net_interfaces: Vec<DomainNetInterfaceXML>,

    /// Input devices
    #[serde(default, rename = "input", skip_serializing_if = "Vec::is_empty")]
    pub inputs: Vec<DomainInputXML>,

    /// TPM device
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tpm: Option<TPMDeviceXML>,
}

/// Graphics information
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "graphics")]
pub struct GraphicsXML {
    #[serde(rename = "@type")]
    pub r#type: String,
    #[serde(rename = "@socket")]
    pub socket: String,
}

/// Video device information
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "video")]
pub struct VideoXML {
    pub model: VideoModelXML,
}

/// Video model device information
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "model")]
pub struct VideoModelXML {
    #[serde(rename = "@type")]
    pub r#type: String,
}

/// Disk information
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "disk")]
pub struct DiskXML {
    #[serde(rename = "@type")]
    pub r#type: String,
    #[serde(rename = "@device")]
    pub r#device: String,

    pub driver: DiskDriverXML,
    pub source: DiskSourceXML,
    pub target: DiskTargetXML,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub readonly: Option<DiskReadOnlyXML>,
    pub boot: DiskBootXML,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub address: Option<DiskAddressXML>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "driver")]
pub struct DiskDriverXML {
    #[serde(rename = "@name")]
    pub name: String,
    #[serde(rename = "@type")]
    pub r#type: String,
    #[serde(default, rename = "@cache")]
    pub r#cache: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "source")]
pub struct DiskSourceXML {
    #[serde(rename = "@file")]
    pub file: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "target")]
pub struct DiskTargetXML {
    #[serde(rename = "@dev")]
    pub dev: String,
    #[serde(rename = "@bus")]
    pub bus: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "readonly")]
pub struct DiskReadOnlyXML {}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "boot")]
pub struct DiskBootXML {
    #[serde(rename = "@order")]
    pub order: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "address")]
pub struct DiskAddressXML {
    #[serde(rename = "@type")]
    pub r#type: String,
    #[serde(
        default,
        skip_serializing_if = "Option::is_none",
        rename = "@controller"
    )]
    pub r#controller: Option<String>,
    #[serde(rename = "@bus")]
    pub r#bus: String,
    #[serde(default, skip_serializing_if = "Option::is_none", rename = "@target")]
    pub r#target: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none", rename = "@unit")]
    pub r#unit: Option<String>,
}

/// Domain RAM information
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "memory")]
pub struct DomainMemoryXML {
    #[serde(rename = "@unit")]
    pub unit: String,

    #[serde(rename = "$value")]
    pub memory: usize,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "topology")]
pub struct DomainCPUTopology {
    #[serde(rename = "@sockets")]
    pub sockets: usize,
    #[serde(rename = "@cores")]
    pub cores: usize,
    #[serde(rename = "@threads")]
    pub threads: usize,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "cpu")]
pub struct DomainVCPUXML {
    #[serde(rename = "$value")]
    pub body: usize,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "cpu")]
pub struct DomainCPUXML {
    #[serde(rename = "@mode")]
    pub mode: String,
    pub topology: Option<DomainCPUTopology>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "entry")]
pub struct OEMStringEntryXML {
    #[serde(rename = "$text", default)]
    pub content: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "oemStrings")]
pub struct OEMStringsXML {
    #[serde(rename = "entry")]
    pub entries: Vec<OEMStringEntryXML>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "sysinfo")]
pub struct SysInfoXML {
    #[serde(rename = "@type")]
    pub r#type: String,
    #[serde(rename = "oemStrings")]
    pub oem_strings: Option<OEMStringsXML>,
}

/// Domain information, see https://libvirt.org/formatdomain.html
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename = "domain")]
pub struct DomainXML {
    /// Domain type (kvm)
    #[serde(rename = "@type")]
    pub r#type: String,

    pub name: String,
    pub uuid: Option<XMLUuid>,
    pub genid: Option<uuid::Uuid>,
    pub title: Option<String>,
    pub description: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub metadata: Option<DomainMetadataXML>,

    pub os: OSXML,
    #[serde(default)]
    pub features: FeaturesXML,
    pub devices: DevicesXML,

    /// The maximum allocation of memory for the guest at boot time
    pub memory: DomainMemoryXML,

    /// Number of vCPU
    pub vcpu: DomainVCPUXML,

    /// CPU information
    pub cpu: DomainCPUXML,

    /// SMBios strings
    pub sysinfo: Option<SysInfoXML>,

    /// Behavior when guest state change
    pub on_poweroff: String,
    pub on_reboot: String,
    pub on_crash: String,
}

const METADATA_START_MARKER: &str =
    "<virtweb:metadata xmlns:virtweb=\"https://virtweb.communiquons.org\">";
const METADATA_END_MARKER: &str = "</virtweb:metadata>";

impl DomainXML {
    /// Decode Domain structure from XML definition
    pub fn parse_xml(xml: &str) -> anyhow::Result<Self> {
        let mut res: Self = quick_xml::de::from_str(xml)?;

        // Handle custom metadata parsing issue
        //
        // https://github.com/tafia/quick-xml/pull/797
        if xml.contains(METADATA_START_MARKER) && xml.contains(METADATA_END_MARKER) {
            let s = xml
                .split_once(METADATA_START_MARKER)
                .unwrap()
                .1
                .split_once(METADATA_END_MARKER)
                .unwrap()
                .0;
            let s = format!("<virtweb>{s}</virtweb>");
            let metadata: DomainMetadataVirtWebXML = quick_xml::de::from_str(&s)?;
            res.metadata = Some(DomainMetadataXML { virtweb: metadata });
        }

        Ok(res)
    }

    /// Turn this domain into its XML definition
    pub fn as_xml(&self) -> anyhow::Result<String> {
        Ok(quick_xml::se::to_string(self)?)
    }
}

/// Domain state
#[derive(serde::Serialize, Debug, Copy, Clone)]
pub enum DomainState {
    NoState,
    Running,
    Blocked,
    Paused,
    Shutdown,
    Shutoff,
    Crashed,
    PowerManagementSuspended,
    Other,
}