use std::collections::{BTreeMap, BTreeSet};
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
use std::sync::{Arc, Weak};

use color_eyre::eyre::eyre;
use imbl_value::InternedString;
use nix::net::if_::if_nametoindex;
use patch_db::json_ptr::JsonPointer;
use tokio::process::Command;
use tokio::sync::Mutex;
use tokio::task::JoinHandle;
use tokio_rustls::rustls::ClientConfig as TlsClientConfig;
use tracing::instrument;

use crate::db::model::Database;
use crate::hostname::ServerHostname;
use crate::net::dns::DnsController;
use crate::net::forward::{
    ForwardRequirements, InterfacePortForwardController, START9_BRIDGE_IFACE, add_iptables_rule,
};
use crate::net::gateway::NetworkInterfaceController;
use crate::net::host::address::HostAddress;
use crate::net::host::binding::{AddSslOptions, BindId, BindOptions};
use crate::net::host::{Host, Hosts, host_for};
use crate::net::service_interface::HostnameMetadata;
use crate::net::socks::SocksController;
use crate::net::vhost::{AlpnInfo, DynVHostTarget, ProxyTarget, VHostController};
use crate::prelude::*;
use crate::service::effects::callbacks::ServiceCallbacks;
use crate::util::Invoke;
use crate::util::serde::MaybeUtf8String;
use crate::util::sync::Watch;
use crate::{GatewayId, HOST_IP, HostId, OptionExt, PackageId};

pub struct NetController {
    pub(crate) db: TypedPatchDb<Database>,
    pub(super) vhost: VHostController,
    pub(super) tls_client_config: Arc<TlsClientConfig>,
    pub(crate) net_iface: Arc<NetworkInterfaceController>,
    pub(super) dns: DnsController,
    pub(super) forward: InterfacePortForwardController,
    pub(super) socks: SocksController,
    pub(crate) callbacks: Arc<ServiceCallbacks>,
}

impl NetController {
    pub async fn init(db: TypedPatchDb<Database>, socks_listen: SocketAddr) -> Result<Self, Error> {
        let net_iface = Arc::new(NetworkInterfaceController::new(db.clone()));
        let socks = SocksController::new(socks_listen)?;
        let crypto_provider = Arc::new(tokio_rustls::rustls::crypto::ring::default_provider());
        let tls_client_config = Arc::new(crate::net::tls::client_config(
            crypto_provider.clone(),
            [&*db
                .peek()
                .await
                .as_private()
                .as_key_store()
                .as_local_certs()
                .as_root_cert()
                .de()?
                .0],
        )?);
        add_iptables_rule(
            false,
            false,
            &[
                "FORWARD",
                "-i",
                START9_BRIDGE_IFACE,
                "-m",
                "state",
                "--state",
                "NEW",
                "-j",
                "ACCEPT",
            ],
        )
        .await?;
        let passthroughs = db
            .peek()
            .await
            .as_public()
            .as_server_info()
            .as_network()
            .as_passthroughs()
            .de()?;
        Ok(Self {
            db: db.clone(),
            vhost: VHostController::new(
                db.clone(),
                net_iface.clone(),
                crypto_provider,
                passthroughs,
            ),
            tls_client_config,
            dns: DnsController::init(db, &net_iface.watcher).await?,
            forward: InterfacePortForwardController::new(net_iface.watcher.subscribe()),
            net_iface,
            socks,
            callbacks: Arc::new(ServiceCallbacks::default()),
        })
    }

    #[instrument(skip_all)]
    pub async fn create_service(
        self: &Arc<Self>,
        package: PackageId,
        ip: Ipv4Addr,
    ) -> Result<NetService, Error> {
        let dns = self.dns.add_service(Some(package.clone()), ip)?;

        let res = NetService::new(NetServiceData {
            id: Some(package),
            ip,
            _dns: dns,
            controller: Arc::downgrade(self),
            binds: BTreeMap::new(),
        })?;
        res.clear_bindings(Default::default()).await?;
        Ok(res)
    }

    pub async fn os_bindings(self: &Arc<Self>) -> Result<NetService, Error> {
        let dns = self.dns.add_service(None, HOST_IP.into())?;

        let service = NetService::new(NetServiceData {
            id: None,
            ip: [127, 0, 0, 1].into(),
            _dns: dns,
            controller: Arc::downgrade(self),
            binds: BTreeMap::new(),
        })?;
        service.clear_bindings(Default::default()).await?;
        service
            .bind(
                HostId::default(),
                80,
                BindOptions {
                    preferred_external_port: 80,
                    add_ssl: Some(AddSslOptions {
                        preferred_external_port: 443,
                        add_x_forwarded_headers: false,
                        alpn: Some(AlpnInfo::Specified(vec![
                            MaybeUtf8String("h2".into()),
                            MaybeUtf8String("http/1.1".into()),
                        ])),
                    }),
                    secure: None,
                },
            )
            .await?;

        Ok(service)
    }
}

#[derive(Default, Debug)]
struct HostBinds {
    forwards: BTreeMap<u16, (SocketAddrV4, ForwardRequirements, Arc<()>)>,
    vhosts: BTreeMap<(Option<InternedString>, u16), (ProxyTarget, Arc<()>)>,
    private_dns: BTreeMap<InternedString, Arc<()>>,
}

pub struct NetServiceData {
    id: Option<PackageId>,
    ip: Ipv4Addr,
    _dns: Arc<()>,
    controller: Weak<NetController>,
    binds: BTreeMap<HostId, HostBinds>,
}
impl NetServiceData {
    fn net_controller(&self) -> Result<Arc<NetController>, Error> {
        Weak::upgrade(&self.controller).ok_or_else(|| {
            Error::new(
                eyre!("NetController is shutdown"),
                crate::ErrorKind::Network,
            )
        })
    }

    async fn update(&mut self, ctrl: &NetController, id: HostId, host: Host) -> Result<(), Error> {
        let mut forwards: BTreeMap<u16, (SocketAddrV4, ForwardRequirements)> = BTreeMap::new();
        let mut vhosts: BTreeMap<(Option<InternedString>, u16), ProxyTarget> = BTreeMap::new();
        let mut private_dns: BTreeSet<InternedString> = BTreeSet::new();
        let binds = self.binds.entry(id.clone()).or_default();

        let net_ifaces = ctrl.net_iface.watcher.ip_info();
        let host_addresses: Vec<_> = host.addresses().collect();

        // Collect private DNS entries (domains without public config)
        for HostAddress {
            address, public, ..
        } in &host_addresses
        {
            if public.is_none() {
                private_dns.insert(address.clone());
            }
        }

        // ── Build controller entries from enabled addresses ──
        for (port, bind) in host.bindings.iter() {
            if !bind.enabled {
                continue;
            }
            if bind.net.assigned_port.is_none() && bind.net.assigned_ssl_port.is_none() {
                continue;
            }

            let enabled_addresses = bind.addresses.enabled();
            let addr: SocketAddr = (self.ip, *port).into();

            // SSL vhosts
            if let Some(ssl) = &bind.options.add_ssl {
                let connect_ssl = if let Some(alpn) = ssl.alpn.clone() {
                    Err(alpn)
                } else if bind.options.secure.as_ref().map_or(false, |s| s.ssl) {
                    Ok(())
                } else {
                    Err(AlpnInfo::Reflect)
                };

                if let Some(assigned_ssl_port) = bind.net.assigned_ssl_port {
                    // Collect private IPs from enabled private addresses' gateways
                    let server_private_ips: BTreeSet<IpAddr> = enabled_addresses
                        .iter()
                        .filter(|a| !a.public)
                        .flat_map(|a| a.metadata.gateways())
                        .filter_map(|gw| net_ifaces.get(gw).and_then(|info| info.ip_info.as_ref()))
                        .flat_map(|ip_info| ip_info.subnets.iter().map(|s| s.addr()))
                        .collect();

                    // Collect public gateways from enabled public IP addresses
                    let server_public_gateways: BTreeSet<GatewayId> = enabled_addresses
                        .iter()
                        .filter(|a| a.public && a.metadata.is_ip())
                        .flat_map(|a| a.metadata.gateways())
                        .cloned()
                        .collect();

                    // * vhost (on assigned_ssl_port)
                    if !server_private_ips.is_empty() || !server_public_gateways.is_empty() {
                        vhosts.insert(
                            (None, assigned_ssl_port),
                            ProxyTarget {
                                public: server_public_gateways.clone(),
                                private: server_private_ips.clone(),
                                acme: None,
                                addr,
                                add_x_forwarded_headers: ssl.add_x_forwarded_headers,
                                connect_ssl: connect_ssl
                                    .clone()
                                    .map(|_| ctrl.tls_client_config.clone()),
                                passthrough: false,
                            },
                        );
                    }
                }

                // Domain vhosts: group by (domain, ssl_port), merge public/private sets
                for addr_info in &enabled_addresses {
                    if !addr_info.ssl {
                        continue;
                    }
                    match &addr_info.metadata {
                        HostnameMetadata::PublicDomain { .. }
                        | HostnameMetadata::PrivateDomain { .. } => {}
                        _ => continue,
                    }
                    let domain = &addr_info.hostname;
                    let Some(domain_ssl_port) = addr_info.port else {
                        continue;
                    };
                    let key = (Some(domain.clone()), domain_ssl_port);
                    let target = vhosts.entry(key).or_insert_with(|| ProxyTarget {
                        public: BTreeSet::new(),
                        private: BTreeSet::new(),
                        acme: host_addresses
                            .iter()
                            .find(|a| a.address == *domain)
                            .and_then(|a| a.public.as_ref())
                            .and_then(|p| p.acme.clone()),
                        addr,
                        add_x_forwarded_headers: ssl.add_x_forwarded_headers,
                        connect_ssl: connect_ssl.clone().map(|_| ctrl.tls_client_config.clone()),
                        passthrough: false,
                    });
                    if addr_info.public {
                        for gw in addr_info.metadata.gateways() {
                            target.public.insert(gw.clone());
                        }
                    } else {
                        for gw in addr_info.metadata.gateways() {
                            if let Some(info) = net_ifaces.get(gw) {
                                if let Some(ip_info) = &info.ip_info {
                                    for subnet in &ip_info.subnets {
                                        target.private.insert(subnet.addr());
                                    }
                                }
                            }
                        }
                    }
                }
            }

            // Non-SSL forwards
            if bind
                .options
                .secure
                .map_or(true, |s| !(s.ssl && bind.options.add_ssl.is_some()))
            {
                let external = bind.net.assigned_port.or_not_found("assigned lan port")?;
                let fwd_public: BTreeSet<GatewayId> = enabled_addresses
                    .iter()
                    .filter(|a| a.public)
                    .flat_map(|a| a.metadata.gateways())
                    .cloned()
                    .collect();
                let fwd_private: BTreeSet<IpAddr> = enabled_addresses
                    .iter()
                    .filter(|a| !a.public)
                    .flat_map(|a| a.metadata.gateways())
                    .filter_map(|gw| net_ifaces.get(gw).and_then(|i| i.ip_info.as_ref()))
                    .flat_map(|ip| ip.subnets.iter().map(|s| s.addr()))
                    .collect();
                forwards.insert(
                    external,
                    (
                        SocketAddrV4::new(self.ip, *port),
                        ForwardRequirements {
                            public_gateways: fwd_public,
                            private_ips: fwd_private,
                            secure: bind.options.secure.is_some(),
                        },
                    ),
                );
            }

            // Passthrough vhosts: if the service handles its own TLS
            // (secure.ssl && no add_ssl) and a domain address is enabled on
            // an SSL port different from assigned_port, add a passthrough
            // vhost so the service's TLS endpoint is reachable on that port.
            if bind.options.secure.map_or(false, |s| s.ssl) && bind.options.add_ssl.is_none() {
                let assigned = bind.net.assigned_port;
                for addr_info in &enabled_addresses {
                    if !addr_info.ssl {
                        continue;
                    }
                    let Some(pt_port) = addr_info.port.filter(|p| assigned != Some(*p)) else {
                        continue;
                    };
                    match &addr_info.metadata {
                        HostnameMetadata::PublicDomain { .. }
                        | HostnameMetadata::PrivateDomain { .. } => {}
                        _ => continue,
                    }
                    let domain = &addr_info.hostname;
                    let key = (Some(domain.clone()), pt_port);
                    let target = vhosts.entry(key).or_insert_with(|| ProxyTarget {
                        public: BTreeSet::new(),
                        private: BTreeSet::new(),
                        acme: None,
                        addr,
                        add_x_forwarded_headers: false,
                        connect_ssl: Err(AlpnInfo::Reflect),
                        passthrough: true,
                    });
                    if addr_info.public {
                        for gw in addr_info.metadata.gateways() {
                            target.public.insert(gw.clone());
                        }
                    } else {
                        for gw in addr_info.metadata.gateways() {
                            if let Some(info) = net_ifaces.get(gw) {
                                if let Some(ip_info) = &info.ip_info {
                                    for subnet in &ip_info.subnets {
                                        target.private.insert(subnet.addr());
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        // ── Phase 3: Reconcile ──
        let all = binds
            .forwards
            .keys()
            .chain(forwards.keys())
            .copied()
            .collect::<BTreeSet<_>>();
        for external in all {
            let mut prev = binds.forwards.remove(&external);
            if let Some((internal, reqs)) = forwards.remove(&external) {
                prev = prev.filter(|(i, r, _)| i == &internal && *r == reqs);
                binds.forwards.insert(
                    external,
                    if let Some(prev) = prev {
                        prev
                    } else {
                        (
                            internal,
                            reqs.clone(),
                            ctrl.forward
                                .add(
                                    external,
                                    reqs,
                                    internal,
                                    net_ifaces
                                        .iter()
                                        .find_map(|(_, i)| {
                                            i.ip_info.as_ref().and_then(|i| {
                                                i.subnets.iter().find(|i| {
                                                    i.contains(&IpAddr::from(*internal.ip()))
                                                })
                                            })
                                        })
                                        .map(|s| s.prefix_len())
                                        .unwrap_or(32),
                                )
                                .await?,
                        )
                    },
                );
            }
        }
        ctrl.forward.gc().await?;

        let all = binds
            .vhosts
            .keys()
            .chain(vhosts.keys())
            .cloned()
            .collect::<BTreeSet<_>>();
        for key in all {
            let mut prev = binds.vhosts.remove(&key);
            if let Some(target) = vhosts.remove(&key) {
                prev = prev.filter(|(t, _)| t == &target);
                binds.vhosts.insert(
                    key.clone(),
                    if let Some(prev) = prev {
                        prev
                    } else {
                        (
                            target.clone(),
                            ctrl.vhost.add(key.0, key.1, DynVHostTarget::new(target))?,
                        )
                    },
                );
            } else {
                if let Some((_, rc)) = prev {
                    drop(rc);
                    ctrl.vhost.gc(key.0, key.1);
                }
            }
        }

        let mut rm = BTreeSet::new();
        binds.private_dns.retain(|fqdn, _| {
            if private_dns.remove(fqdn) {
                true
            } else {
                rm.insert(fqdn.clone());
                false
            }
        });
        for fqdn in private_dns {
            binds
                .private_dns
                .insert(fqdn.clone(), ctrl.dns.add_private_domain(fqdn)?);
        }
        ctrl.dns.gc_private_domains(&rm)?;

        Ok(())
    }
}

pub struct NetService {
    shutdown: bool,
    data: Arc<Mutex<NetServiceData>>,
    sync_task: JoinHandle<()>,
    synced: Watch<u64>,
}
impl NetService {
    fn dummy() -> Self {
        Self {
            shutdown: true,
            data: Arc::new(Mutex::new(NetServiceData {
                id: None,
                ip: Ipv4Addr::new(0, 0, 0, 0),
                _dns: Default::default(),
                controller: Default::default(),
                binds: BTreeMap::new(),
            })),
            sync_task: tokio::spawn(futures::future::ready(())),
            synced: Watch::new(0u64),
        }
    }

    fn new(data: NetServiceData) -> Result<Self, Error> {
        let ctrl = data.net_controller()?;
        let pkg_id = data.id.clone();
        let db = ctrl.db.clone();
        drop(ctrl);

        let synced = Watch::new(0u64);
        let synced_writer = synced.clone();

        let ip = data.ip;
        let data = Arc::new(Mutex::new(data));
        let thread_data = data.clone();
        let sync_task = tokio::spawn(async move {
            if let Some(ref id) = pkg_id {
                let ptr: JsonPointer = format!("/public/packageData/{}/hosts", id).parse().unwrap();
                let mut watch = db.watch(ptr).await.typed::<Hosts>();

                // Outbound gateway enforcement
                let service_ip = ip.to_string();
                // Purge any stale rules from a previous instance
                loop {
                    if Command::new("ip")
                        .arg("rule")
                        .arg("del")
                        .arg("from")
                        .arg(&service_ip)
                        .arg("priority")
                        .arg("100")
                        .invoke(ErrorKind::Network)
                        .await
                        .is_err()
                    {
                        break;
                    }
                }
                let mut outbound_sub = db
                    .subscribe(
                        format!("/public/packageData/{}/outboundGateway", id)
                            .parse::<JsonPointer<_, _>>()
                            .unwrap(),
                    )
                    .await;
                let ctrl_for_ip = thread_data.lock().await.net_controller().ok();
                let mut ip_info_watch = ctrl_for_ip
                    .as_ref()
                    .map(|c| c.net_iface.watcher.subscribe());
                if let Some(ref mut w) = ip_info_watch {
                    w.mark_seen();
                }
                drop(ctrl_for_ip);
                let mut current_outbound_table: Option<u32> = None;

                loop {
                    let (hosts_changed, outbound_changed) = tokio::select! {
                        res = watch.changed() => {
                            if let Err(e) = res {
                                tracing::error!("DB watch disconnected for {id}: {e}");
                                break;
                            }
                            (true, false)
                        }
                        _ = outbound_sub.recv() => (false, true),
                        _ = async {
                            if let Some(ref mut w) = ip_info_watch {
                                w.changed().await;
                            } else {
                                std::future::pending::<()>().await;
                            }
                        } => (false, true),
                    };

                    // Handle host updates
                    if hosts_changed {
                        if let Err(e) = async {
                            let hosts = watch.peek()?.de()?;
                            let mut data = thread_data.lock().await;
                            let ctrl = data.net_controller()?;
                            for (host_id, host) in hosts.0 {
                                data.update(&*ctrl, host_id, host).await?;
                            }
                            Ok::<_, Error>(())
                        }
                        .await
                        {
                            tracing::error!("Failed to update network info for {id}: {e}");
                            tracing::debug!("{e:?}");
                        }
                    }

                    // Handle outbound gateway changes
                    if outbound_changed {
                        if let Err(e) = async {
                            // Remove old rule if any
                            if let Some(old_table) = current_outbound_table.take() {
                                let old_table_str = old_table.to_string();
                                let _ = Command::new("ip")
                                    .arg("rule")
                                    .arg("del")
                                    .arg("from")
                                    .arg(&service_ip)
                                    .arg("lookup")
                                    .arg(&old_table_str)
                                    .arg("priority")
                                    .arg("100")
                                    .invoke(ErrorKind::Network)
                                    .await;
                            }
                            // Read current outbound gateway from DB
                            let outbound_gw: Option<GatewayId> = db
                                .peek()
                                .await
                                .as_public()
                                .as_package_data()
                                .as_idx(id)
                                .map(|p| p.as_outbound_gateway().de().ok())
                                .flatten()
                                .flatten();
                            if let Some(gw_id) = outbound_gw {
                                // Look up table ID for this gateway
                                if let Some(table_id) = if_nametoindex(gw_id.as_str())
                                    .map(|idx| 1000 + idx)
                                    .log_err()
                                {
                                    let table_str = table_id.to_string();
                                    Command::new("ip")
                                        .arg("rule")
                                        .arg("add")
                                        .arg("from")
                                        .arg(&service_ip)
                                        .arg("lookup")
                                        .arg(&table_str)
                                        .arg("priority")
                                        .arg("100")
                                        .invoke(ErrorKind::Network)
                                        .await
                                        .log_err();
                                    current_outbound_table = Some(table_id);
                                }
                            }
                            Ok::<_, Error>(())
                        }
                        .await
                        {
                            tracing::error!("Failed to update outbound gateway for {id}: {e}");
                            tracing::debug!("{e:?}");
                        }
                    }

                    synced_writer.send_modify(|v| *v += 1);
                }

                // Cleanup outbound rule on task exit
                if let Some(table_id) = current_outbound_table {
                    let table_str = table_id.to_string();
                    let _ = Command::new("ip")
                        .arg("rule")
                        .arg("del")
                        .arg("from")
                        .arg(&service_ip)
                        .arg("lookup")
                        .arg(&table_str)
                        .arg("priority")
                        .arg("100")
                        .invoke(ErrorKind::Network)
                        .await;
                }
            } else {
                let ptr: JsonPointer = "/public/serverInfo/network/host".parse().unwrap();
                let mut watch = db.watch(ptr).await.typed::<Host>();
                loop {
                    if let Err(e) = watch.changed().await {
                        tracing::error!("DB watch disconnected for Main UI: {e}");
                        break;
                    }
                    if let Err(e) = async {
                        let host = watch.peek()?.de()?;
                        let mut data = thread_data.lock().await;
                        let ctrl = data.net_controller()?;
                        data.update(&*ctrl, HostId::default(), host).await?;
                        Ok::<_, Error>(())
                    }
                    .await
                    {
                        tracing::error!("Failed to update network info for Main UI: {e}");
                        tracing::debug!("{e:?}");
                    }
                    synced_writer.send_modify(|v| *v += 1);
                }
            }
        });

        Ok(Self {
            shutdown: false,
            data,
            sync_task,
            synced,
        })
    }

    pub async fn bind(
        &self,
        id: HostId,
        internal_port: u16,
        options: BindOptions,
    ) -> Result<(), Error> {
        let (ctrl, pkg_id) = {
            let data = self.data.lock().await;
            (data.net_controller()?, data.id.clone())
        };
        ctrl.db
            .mutate(|db| {
                let gateways = db
                    .as_public()
                    .as_server_info()
                    .as_network()
                    .as_gateways()
                    .de()?;
                let hostname = ServerHostname::load(db.as_public().as_server_info())?;
                let mut ports = db.as_private().as_available_ports().de()?;
                let host = host_for(db, pkg_id.as_ref(), &id)?;
                host.add_binding(&mut ports, internal_port, options)?;
                host.update_addresses(&hostname, &gateways, &ports)?;
                db.as_private_mut().as_available_ports_mut().ser(&ports)?;
                Ok(())
            })
            .await
            .result
    }

    pub async fn clear_bindings(&self, except: BTreeSet<BindId>) -> Result<(), Error> {
        let (ctrl, pkg_id) = {
            let data = self.data.lock().await;
            (data.net_controller()?, data.id.clone())
        };
        ctrl.db
            .mutate(|db| {
                let gateways = db
                    .as_public()
                    .as_server_info()
                    .as_network()
                    .as_gateways()
                    .de()?;
                let hostname = ServerHostname::load(db.as_public().as_server_info())?;
                let ports = db.as_private().as_available_ports().de()?;
                if let Some(ref pkg_id) = pkg_id {
                    for (host_id, host) in db
                        .as_public_mut()
                        .as_package_data_mut()
                        .as_idx_mut(pkg_id)
                        .or_not_found(pkg_id)?
                        .as_hosts_mut()
                        .as_entries_mut()?
                    {
                        host.as_bindings_mut().mutate(|b| {
                            for (internal_port, info) in b.iter_mut() {
                                if !except.contains(&BindId {
                                    id: host_id.clone(),
                                    internal_port: *internal_port,
                                }) {
                                    info.disable();
                                }
                            }
                            Ok(())
                        })?;
                        host.update_addresses(&hostname, &gateways, &ports)?;
                    }
                } else {
                    let host = db
                        .as_public_mut()
                        .as_server_info_mut()
                        .as_network_mut()
                        .as_host_mut();
                    host.as_bindings_mut().mutate(|b| {
                        for (internal_port, info) in b.iter_mut() {
                            if !except.contains(&BindId {
                                id: HostId::default(),
                                internal_port: *internal_port,
                            }) {
                                info.disable();
                            }
                        }
                        Ok(())
                    })?;
                    host.update_addresses(&hostname, &gateways, &ports)?;
                }
                Ok(())
            })
            .await
            .result
    }

    pub async fn remove_all(mut self) -> Result<(), Error> {
        if Weak::upgrade(&self.data.lock().await.controller).is_none() {
            self.shutdown = true;
            tracing::warn!("NetService dropped after NetController is shutdown");
            return Err(Error::new(
                eyre!("NetController is shutdown"),
                crate::ErrorKind::Network,
            ));
        }
        let current = self.synced.peek(|v| *v);
        self.clear_bindings(Default::default()).await?;
        let mut w = self.synced.clone();
        w.wait_for(|v| *v > current).await;
        self.sync_task.abort();
        // Clean up any outbound gateway ip rules for this service
        let service_ip = self.data.lock().await.ip.to_string();
        loop {
            if Command::new("ip")
                .arg("rule")
                .arg("del")
                .arg("from")
                .arg(&service_ip)
                .arg("priority")
                .arg("100")
                .invoke(ErrorKind::Network)
                .await
                .is_err()
            {
                break;
            }
        }
        Ok(())
    }

    pub async fn get_ip(&self) -> Ipv4Addr {
        self.data.lock().await.ip
    }
}

impl Drop for NetService {
    fn drop(&mut self) {
        if !self.shutdown {
            self.shutdown = true;
            let svc = std::mem::replace(self, Self::dummy());
            tokio::spawn(async move { svc.remove_all().await.log_err() });
        }
    }
}
