use std::collections::BTreeSet;

use anyhow::Context as _;
use common::proc::Command;
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Link {
    ifname: String,
}

#[derive(Debug, Deserialize)]
struct Addr {
    addr_info: Vec<AddrInfo>,
}

#[derive(Debug, Deserialize)]
struct AddrInfo {
    local: String,
    prefixlen: usize,
}

#[derive(Debug, Deserialize)]
struct Route {
    dst: String,
    gateway: Option<String>,
}

pub fn get_link_names() -> anyhow::Result<BTreeSet<String>> {
    let mut proc = Command::new("ip");
    proc.args(["-j", "link"]);
    let out = proc.try_spawn_to_json::<Vec<Link>>()?;

    Ok(out.into_iter().map(|l| l.ifname).collect())
}

pub fn has_ipv6_address(dev: &str, addr: &str) -> anyhow::Result<bool> {
    let mut proc = Command::new("ip");
    proc.args(["-j", "-6", "addr", "show", dev]);

    let out: Vec<Addr> = proc.try_spawn_to_json()?;

    let idx = addr
        .char_indices()
        .find_map(|(idx, ch)| if ch == '/' { Some(idx) } else { None })
        .expect("Address should contain prefixlen");
    let prefixlen = &addr[idx + 1..];
    let addr = &addr[..idx];
    let prefixlen: usize = prefixlen
        .parse()
        .context("Ipv6 prefixlen was not a usize")?;
    Ok(out.iter().any(|a| {
        a.addr_info
            .iter()
            .any(|a| a.local == addr && a.prefixlen == prefixlen)
    }))
}

pub fn has_default_route(dev: &str, gateway: &str) -> anyhow::Result<bool> {
    let mut proc = Command::new("ip");
    proc.args(["-j", "-6", "route", "show", "dev", dev]);

    let out: Vec<Route> = proc.try_spawn_to_json()?;

    Ok(out
        .iter()
        .any(|r| r.dst == "default" && r.gateway.as_deref().is_some_and(|gw| gw == gateway)))
}
