//! mDNS advertisement for the local peer server.

use std::{collections::HashMap, net::SocketAddr, time::Duration};

use lanspread_mdns::{DaemonEvent, LANSPREAD_SERVICE_TYPE, MdnsAdvertiser, MdnsMonitor};
use lanspread_proto::PROTOCOL_VERSION;
use tokio_util::sync::CancellationToken;

use crate::{context::PeerCtx, network::select_advertise_ip};

pub(super) async fn start_mdns_advertiser(
    ctx: &PeerCtx,
    server_addr: SocketAddr,
) -> eyre::Result<MdnsAdvertiser> {
    let advertise_ip = select_advertise_ip()?;
    let advertise_addr = SocketAddr::new(advertise_ip, server_addr.port());
    log::info!("Advertising peer via mDNS from {advertise_addr}");

    {
        let mut guard = ctx.local_peer_addr.write().await;
        *guard = Some(advertise_addr);
    }

    let peer_id = ctx.peer_id.as_ref().clone();
    let hostname = gethostname::gethostname().to_string_lossy().into_owned();
    let advertised_name = advertised_service_name(&hostname, &peer_id);
    let monitor_name = advertised_name.clone();
    let properties = advertisement_properties(ctx, &hostname, &peer_id).await;

    let mdns = tokio::task::spawn_blocking(move || {
        MdnsAdvertiser::new(
            LANSPREAD_SERVICE_TYPE,
            &advertised_name,
            advertise_addr,
            Some(properties),
        )
    })
    .await??;

    log::info!("Registered mDNS service with name: {monitor_name}");
    Ok(mdns)
}

pub(super) async fn monitor_mdns_events(monitor: MdnsMonitor, shutdown: CancellationToken) {
    loop {
        let event = tokio::select! {
            () = shutdown.cancelled() => break,
            event = monitor.recv_async() => event,
        };

        match event {
            Ok(DaemonEvent::Error(err)) => {
                log::error!("mDNS error: {err}");
                tokio::select! {
                    () = shutdown.cancelled() => break,
                    () = tokio::time::sleep(Duration::from_secs(1)) => {}
                }
            }
            Ok(other_event) => {
                log::trace!("mDNS event: {other_event:?}");
            }
            Err(err) => {
                log::debug!("mDNS monitor channel closed: {err}");
                break;
            }
        }
    }
}

fn advertised_service_name(hostname: &str, peer_id: &str) -> String {
    let max_hostname_len = 63usize.saturating_sub(peer_id.len() + 1);
    let truncated_hostname = if hostname.len() > max_hostname_len {
        hostname.get(..max_hostname_len).unwrap_or(hostname)
    } else {
        hostname
    };

    if truncated_hostname.is_empty() {
        peer_id.to_string()
    } else {
        format!("{truncated_hostname}-{peer_id}")
    }
}

async fn advertisement_properties(
    ctx: &PeerCtx,
    hostname: &str,
    peer_id: &str,
) -> HashMap<String, String> {
    let (library_rev, library_digest) = {
        let library_guard = ctx.local_library.read().await;
        (library_guard.revision, library_guard.digest)
    };

    let mut properties = HashMap::new();
    properties.insert("peer_id".to_string(), peer_id.to_string());
    properties.insert("proto_ver".to_string(), PROTOCOL_VERSION.to_string());
    properties.insert("library_rev".to_string(), library_rev.to_string());
    properties.insert("library_digest".to_string(), library_digest.to_string());
    if !hostname.is_empty() {
        properties.insert("hostname".to_string(), hostname.to_string());
    }
    properties
}
