//! Wires the pieces together: UDP media socket + Unix control socket + SFU loop.
//! Shared by `main.rs` and the integration tests.

use std::net::{IpAddr, SocketAddr, UdpSocket};
use std::os::unix::net::{UnixListener, UnixStream};
use std::path::PathBuf;
use std::sync::mpsc;
use std::sync::{Arc, Mutex};
use std::thread;

use anyhow::{bail, Context};
use log::info;

use crate::control;
use crate::sfu::Sfu;

pub struct Config {
    /// Unix socket path for the JSON control protocol.
    pub socket: PathBuf,
    /// Bind address for the shared media UDP socket.
    pub udp_ip: IpAddr,
    /// UDP port (0 = ephemeral).
    pub udp_port: u16,
    /// IP advertised to browsers in the host ICE candidate. Defaults to the
    /// default-route interface address (or the bind ip when it is specific).
    pub public_ip: Option<IpAddr>,
}

/// Run the sidecar until a `shutdown` control command. Blocking.
pub fn run(config: Config) -> anyhow::Result<()> {
    // Refuse to clobber a live sidecar; clean up a stale socket file.
    if config.socket.exists() {
        if UnixStream::connect(&config.socket).is_ok() {
            bail!(
                "another sidecar is already listening on {}",
                config.socket.display()
            );
        }
        std::fs::remove_file(&config.socket).context("removing stale control socket")?;
    }
    if let Some(dir) = config.socket.parent() {
        std::fs::create_dir_all(dir).context("creating control socket directory")?;
    }

    let socket = UdpSocket::bind((config.udp_ip, config.udp_port)).context("binding UDP")?;
    let local = socket.local_addr()?;

    let public_ip = config
        .public_ip
        .or_else(|| (!config.udp_ip.is_unspecified()).then_some(config.udp_ip))
        .or_else(default_route_ip)
        .unwrap_or_else(|| IpAddr::from([127, 0, 0, 1]));
    let advertise = SocketAddr::new(public_ip, local.port());

    let (cmd_tx, cmd_rx) = mpsc::channel();
    let (notice_tx, notice_rx) = mpsc::channel();
    let subscribers: control::Subscribers = Arc::new(Mutex::new(Vec::new()));

    let listener = UnixListener::bind(&config.socket).context("binding control socket")?;
    {
        let subscribers = subscribers.clone();
        thread::spawn(move || control::serve(listener, cmd_tx, subscribers));
    }
    thread::spawn(move || control::broadcast_notices(notice_rx, subscribers));

    info!(
        "blax-webrtc-sfu ready — control {} · media udp {} (advertising {})",
        config.socket.display(),
        local,
        advertise
    );

    Sfu::new(socket, advertise, cmd_rx, notice_tx).run();

    let _ = std::fs::remove_file(&config.socket);
    Ok(())
}

/// The local IP of the default route (no packets are sent — UDP connect only
/// resolves the source address).
fn default_route_ip() -> Option<IpAddr> {
    let probe = UdpSocket::bind("0.0.0.0:0").ok()?;
    probe.connect("8.8.8.8:80").ok()?;
    Some(probe.local_addr().ok()?.ip())
}
