use crate::actors::libvirt_actor;
use crate::actors::libvirt_actor::LibVirtActor;
use crate::libvirt_lib_structures::XMLUuid;
use crate::libvirt_lib_structures::domain::{DomainState, DomainXML};
use crate::libvirt_lib_structures::network::NetworkXML;
use crate::libvirt_lib_structures::nwfilter::NetworkFilterXML;
use crate::libvirt_rest_structures::hypervisor::HypervisorInfo;
use crate::libvirt_rest_structures::net::NetworkInfo;
use crate::libvirt_rest_structures::nw_filter::NetworkFilter;
use crate::libvirt_rest_structures::vm::{VMGroupId, VMInfo};
use actix::Addr;
use std::collections::HashSet;

#[derive(Clone)]
pub struct LibVirtClient(pub Addr<LibVirtActor>);

impl LibVirtClient {
    /// Get hypervisor info
    pub async fn get_info(&self) -> anyhow::Result<HypervisorInfo> {
        self.0.send(libvirt_actor::GetHypervisorInfo).await?
    }

    /// Get the full list of domain
    pub async fn get_full_domains_list(&self) -> anyhow::Result<Vec<DomainXML>> {
        let ids = self.0.send(libvirt_actor::GetDomainsListReq).await??;
        let mut info = Vec::with_capacity(ids.len());
        for id in ids {
            info.push(self.get_single_domain(id).await?)
        }
        Ok(info)
    }

    /// Get the information about a single domain
    pub async fn get_single_domain(&self, id: XMLUuid) -> anyhow::Result<DomainXML> {
        self.0.send(libvirt_actor::GetDomainXMLReq(id)).await?
    }

    /// Get the source XML configuration of a single domain
    pub async fn get_single_domain_xml(&self, id: XMLUuid) -> anyhow::Result<String> {
        self.0
            .send(libvirt_actor::GetSourceDomainXMLReq(id))
            .await?
    }

    /// Update a domain
    pub async fn update_domain(&self, vm_def: VMInfo, xml: DomainXML) -> anyhow::Result<XMLUuid> {
        self.0
            .send(libvirt_actor::DefineDomainReq(vm_def, xml))
            .await?
    }

    /// Delete a domain
    pub async fn delete_domain(&self, id: XMLUuid, keep_files: bool) -> anyhow::Result<()> {
        self.0
            .send(libvirt_actor::DeleteDomainReq { id, keep_files })
            .await?
    }

    /// Get the state of a domain
    pub async fn get_domain_state(&self, id: XMLUuid) -> anyhow::Result<DomainState> {
        self.0.send(libvirt_actor::GetDomainStateReq(id)).await?
    }

    /// Start a domain
    pub async fn start_domain(&self, id: XMLUuid) -> anyhow::Result<()> {
        self.0.send(libvirt_actor::StartDomainReq(id)).await?
    }

    /// Shutdown a domain
    pub async fn shutdown_domain(&self, id: XMLUuid) -> anyhow::Result<()> {
        self.0.send(libvirt_actor::ShutdownDomainReq(id)).await?
    }

    /// Kill a domain
    pub async fn kill_domain(&self, id: XMLUuid) -> anyhow::Result<()> {
        self.0.send(libvirt_actor::KillDomainReq(id)).await?
    }

    /// Reset a domain
    pub async fn reset_domain(&self, id: XMLUuid) -> anyhow::Result<()> {
        self.0.send(libvirt_actor::ResetDomainReq(id)).await?
    }

    /// Suspend a domain
    pub async fn suspend_domain(&self, id: XMLUuid) -> anyhow::Result<()> {
        self.0.send(libvirt_actor::SuspendDomainReq(id)).await?
    }

    /// Resume a domain
    pub async fn resume_domain(&self, id: XMLUuid) -> anyhow::Result<()> {
        self.0.send(libvirt_actor::ResumeDomainReq(id)).await?
    }

    /// Take a screenshot of the domain
    pub async fn screenshot_domain(&self, id: XMLUuid) -> anyhow::Result<Vec<u8>> {
        self.0.send(libvirt_actor::ScreenshotDomainReq(id)).await?
    }

    /// Get auto-start status of a domain
    pub async fn is_domain_autostart(&self, id: XMLUuid) -> anyhow::Result<bool> {
        self.0.send(libvirt_actor::IsDomainAutostart(id)).await?
    }

    /// Update autostart value of a domain
    pub async fn set_domain_autostart(&self, id: XMLUuid, autostart: bool) -> anyhow::Result<()> {
        self.0
            .send(libvirt_actor::SetDomainAutostart(id, autostart))
            .await?
    }

    /// Get the full list of groups
    pub async fn get_full_groups_list(&self) -> anyhow::Result<Vec<VMGroupId>> {
        let domains = self.get_full_domains_list().await?;
        let mut out = HashSet::new();
        for d in domains {
            if let Some(g) = VMInfo::from_domain(d)?.group {
                out.insert(g);
            }
        }
        let mut out: Vec<_> = out.into_iter().collect();
        out.sort();
        Ok(out)
    }

    /// Get the full list of VMs of a given group
    pub async fn get_full_group_vm_list(
        &self,
        group: &VMGroupId,
    ) -> anyhow::Result<Vec<DomainXML>> {
        let vms = self.get_full_domains_list().await?;
        let mut out = Vec::new();
        for vm in vms {
            if VMInfo::from_domain(vm.clone())?.group == Some(group.clone()) {
                out.push(vm);
            }
        }
        Ok(out)
    }

    /// Update a network configuration
    pub async fn update_network(
        &self,
        net_def: NetworkInfo,
        network: NetworkXML,
    ) -> anyhow::Result<XMLUuid> {
        self.0
            .send(libvirt_actor::DefineNetwork(net_def, network))
            .await?
    }

    /// Get the full list of networks
    pub async fn get_full_networks_list(&self) -> anyhow::Result<Vec<NetworkXML>> {
        let ids = self.0.send(libvirt_actor::GetNetworksListReq).await??;
        let mut info = Vec::with_capacity(ids.len());
        for id in ids {
            info.push(self.get_single_network(id).await?)
        }
        Ok(info)
    }

    /// Get the information about a single network
    pub async fn get_single_network(&self, id: XMLUuid) -> anyhow::Result<NetworkXML> {
        self.0.send(libvirt_actor::GetNetworkXMLReq(id)).await?
    }

    /// Get the source XML configuration of a single network
    pub async fn get_single_network_xml(&self, id: XMLUuid) -> anyhow::Result<String> {
        self.0
            .send(libvirt_actor::GetSourceNetworkXMLReq(id))
            .await?
    }

    /// Delete a network
    pub async fn delete_network(&self, id: XMLUuid) -> anyhow::Result<()> {
        self.0.send(libvirt_actor::DeleteNetwork(id)).await?
    }

    /// Get auto-start status of a network
    pub async fn is_network_autostart(&self, id: XMLUuid) -> anyhow::Result<bool> {
        self.0.send(libvirt_actor::IsNetworkAutostart(id)).await?
    }

    /// Update autostart value of a network
    pub async fn set_network_autostart(&self, id: XMLUuid, autostart: bool) -> anyhow::Result<()> {
        self.0
            .send(libvirt_actor::SetNetworkAutostart(id, autostart))
            .await?
    }

    /// Check out whether a network is started or not
    pub async fn is_network_started(&self, id: XMLUuid) -> anyhow::Result<bool> {
        self.0.send(libvirt_actor::IsNetworkStarted(id)).await?
    }

    /// Start a network
    pub async fn start_network(&self, id: XMLUuid) -> anyhow::Result<()> {
        self.0.send(libvirt_actor::StartNetwork(id)).await?
    }

    /// Stop a network
    pub async fn stop_network(&self, id: XMLUuid) -> anyhow::Result<()> {
        self.0.send(libvirt_actor::StopNetwork(id)).await?
    }

    /// Get the full list of network filters
    pub async fn get_full_network_filters_list(&self) -> anyhow::Result<Vec<NetworkFilterXML>> {
        let ids = self.0.send(libvirt_actor::GetNWFiltersListReq).await??;
        let mut info = Vec::with_capacity(ids.len());
        for id in ids {
            info.push(self.get_single_network_filter(id).await?)
        }
        Ok(info)
    }

    /// Get the information about a single domain
    pub async fn get_single_network_filter(&self, id: XMLUuid) -> anyhow::Result<NetworkFilterXML> {
        self.0.send(libvirt_actor::GetNWFilterXMLReq(id)).await?
    }

    /// Get the source XML configuration of a single network filter
    pub async fn get_single_network_filter_xml(&self, id: XMLUuid) -> anyhow::Result<String> {
        self.0
            .send(libvirt_actor::GetSourceNetworkFilterXMLReq(id))
            .await?
    }

    /// Update the information about a single domain
    pub async fn update_network_filter(
        &self,
        nwf_def: NetworkFilter,
        xml: NetworkFilterXML,
    ) -> anyhow::Result<XMLUuid> {
        self.0
            .send(libvirt_actor::DefineNWFilterReq(nwf_def, xml))
            .await?
    }

    /// Delete a network filter
    pub async fn delete_network_filter(&self, id: XMLUuid) -> anyhow::Result<()> {
        self.0.send(libvirt_actor::DeleteNetworkFilter(id)).await?
    }
}