use crate::provider::{
    FieldSchema, FieldType, InvalidationStrategy, Provider, ProviderMetadata, ProviderResult, Value,
};
use std::net::Ipv4Addr;

#[cfg(target_os = "linux")]
pub mod linux;
#[cfg(target_os = "macos")]
mod macos;

pub struct NetworkProvider;

impl Provider for NetworkProvider {
    fn metadata(&self) -> ProviderMetadata {
        ProviderMetadata {
            name: "network".to_string(),
            fields: vec![
                FieldSchema {
                    name: "interface".to_string(),
                    field_type: FieldType::String,
                },
                FieldSchema {
                    name: "ip".to_string(),
                    field_type: FieldType::String,
                },
                FieldSchema {
                    name: "vpn_active".to_string(),
                    field_type: FieldType::Bool,
                },
                FieldSchema {
                    name: "vpn_name".to_string(),
                    field_type: FieldType::String,
                },
                FieldSchema {
                    name: "ssid".to_string(),
                    field_type: FieldType::String,
                },
                FieldSchema {
                    name: "online".to_string(),
                    field_type: FieldType::Bool,
                },
            ],
            invalidation: InvalidationStrategy::Poll {
                interval_secs: 10,
                floor_secs: 5,
            },
            global: true,
        }
    }

    fn execute(&self, _path: Option<&str>) -> Option<ProviderResult> {
        let (iface, ip, vpn_active, vpn_name) = scan_interfaces();
        let ssid = get_wifi_ssid();

        let mut result = ProviderResult::new();
        result.insert("interface", Value::String(iface));
        result.insert("ip", Value::String(ip.clone()));
        result.insert("online", Value::Bool(!ip.is_empty()));
        result.insert("vpn_active", Value::Bool(vpn_active));
        result.insert("vpn_name", Value::String(vpn_name));
        result.insert("ssid", Value::String(ssid));
        Some(result)
    }
}

fn scan_interfaces() -> (String, String, bool, String) {
    let mut ifaddrs: *mut libc::ifaddrs = std::ptr::null_mut();
    if unsafe { libc::getifaddrs(&mut ifaddrs) } != 0 {
        return (String::new(), String::new(), false, String::new());
    }

    let mut best_iface = String::new();
    let mut best_ip = String::new();
    let mut vpn_active = false;
    let mut vpn_name = String::new();

    let mut curr = ifaddrs;
    while !curr.is_null() {
        let entry = unsafe { &*curr };
        let name = unsafe { std::ffi::CStr::from_ptr(entry.ifa_name) }
            .to_string_lossy()
            .to_string();
        let flags = entry.ifa_flags;

        let is_up = flags & (libc::IFF_UP as u32) != 0;
        let is_loopback = flags & (libc::IFF_LOOPBACK as u32) != 0;

        if is_up && !is_loopback {
            if is_vpn_interface(&name) && !vpn_active {
                vpn_active = true;
                vpn_name = name.clone();
            }

            if !entry.ifa_addr.is_null() {
                let family = unsafe { (*entry.ifa_addr).sa_family } as i32;
                if family == libc::AF_INET {
                    let addr = unsafe { &*(entry.ifa_addr as *const libc::sockaddr_in) };
                    let ip = Ipv4Addr::from(u32::from_be(addr.sin_addr.s_addr));
                    if !ip.is_loopback()
                        && !ip.is_link_local()
                        && (best_iface.is_empty() || is_preferred_interface(&name))
                    {
                        best_iface = name.clone();
                        best_ip = ip.to_string();
                    }
                }
            }
        }

        curr = entry.ifa_next;
    }

    unsafe { libc::freeifaddrs(ifaddrs) };

    (best_iface, best_ip, vpn_active, vpn_name)
}

#[cfg(target_os = "macos")]
fn is_vpn_interface(name: &str) -> bool {
    macos::is_vpn_interface(name)
}
#[cfg(target_os = "linux")]
fn is_vpn_interface(name: &str) -> bool {
    linux::is_vpn_interface(name)
}

#[cfg(target_os = "macos")]
fn is_preferred_interface(name: &str) -> bool {
    macos::is_preferred_interface(name)
}
#[cfg(target_os = "linux")]
fn is_preferred_interface(name: &str) -> bool {
    linux::is_preferred_interface(name)
}

#[cfg(target_os = "macos")]
fn get_wifi_ssid() -> String {
    macos::get_wifi_ssid()
}
#[cfg(target_os = "linux")]
fn get_wifi_ssid() -> String {
    linux::get_wifi_ssid()
}
