use crate::infra::opnsense::Host;
use crate::infra::opnsense::IpAddress;
use crate::infra::opnsense::LogicalHost;
use crate::{
    executors::ExecutorError,
    topology::{DnsRecord, DnsServer},
};
use async_trait::async_trait;

use super::OPNSenseFirewall;

#[async_trait]
impl DnsServer for OPNSenseFirewall {
    async fn register_hosts(&self, hosts: Vec<DnsRecord>) -> Result<(), ExecutorError> {
        let mut writable_opnsense = self.opnsense_config.write().await;
        let mut dns = writable_opnsense.dns();
        let hosts = hosts
            .iter()
            .map(|h| {
                Host::new(
                    h.host.clone(),
                    h.domain.clone(),
                    h.record_type.to_string(),
                    h.value.to_string(),
                )
            })
            .collect();
        dns.register_hosts(hosts);
        Ok(())
    }

    fn remove_record(
        &self,
        _name: &str,
        _record_type: crate::topology::DnsRecordType,
    ) -> Result<(), ExecutorError> {
        todo!()
    }

    async fn list_records(&self) -> Vec<crate::topology::DnsRecord> {
        self.opnsense_config
            .write()
            .await
            .dns()
            .get_hosts()
            .iter()
            .map(|h| DnsRecord {
                host: h.hostname.clone(),
                domain: h.domain.clone(),
                record_type: h
                    .rr
                    .parse()
                    .expect("received invalid record type {h.rr} from opnsense"),
                value: h
                    .server
                    .parse()
                    .expect("received invalid ipv4 record from opnsense {h.server}"),
            })
            .collect()
    }

    fn get_ip(&self) -> IpAddress {
        OPNSenseFirewall::get_ip(&self)
    }

    fn get_host(&self) -> LogicalHost {
        self.host.clone()
    }

    async fn register_dhcp_leases(&self, register: bool) -> Result<(), ExecutorError> {
        let mut writable_opnsense = self.opnsense_config.write().await;
        let mut dns = writable_opnsense.dns();
        dns.register_dhcp_leases(register);

        Ok(())
    }

    async fn commit_config(&self) -> Result<(), ExecutorError> {
        let opnsense = self.opnsense_config.read().await;

        opnsense
            .save()
            .await
            .map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?;

        opnsense
            .restart_dns()
            .await
            .map_err(|e| ExecutorError::UnexpectedError(e.to_string()))
    }
}
