/* src/network/mod.rs
 *
 * Copyright 2025 Mission Center Developers
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

use std::collections::HashMap;

use magpie_platform::network::{Connection, ConnectionKind};

use crate::async_runtime;
use crate::{sync, system_bus};

use device_name::device_name;
use interface_iter::InterfaceIter;
use network_manager::NetworkManagerProxy;

mod device_name;
mod interface_iter;
mod ip;
mod network_manager;
mod stats;
mod util;
mod wireless;

struct TransferStats {
    pub tx_bytes: u64,
    pub rx_bytes: u64,

    pub update_timestamp: std::time::Instant,
}

#[derive(Default)]
pub struct NetworkCache {
    proxy: Option<NetworkManagerProxy<'static>>,

    connections: Vec<Connection>,

    stats_cache: HashMap<String, TransferStats>,
    device_name_cache: HashMap<String, String>,
}

impl magpie_platform::network::NetworkCache for NetworkCache {
    fn new() -> Self
    where
        Self: Sized,
    {
        let dbus_connection = match system_bus() {
            Some(c) => c,
            None => {
                log::warn!(
                    "Failed to connect to system bus, network information will not be available"
                );
                return Self::default();
            }
        };

        let rt = async_runtime();

        let proxy = match sync!(rt, NetworkManagerProxy::new(&dbus_connection)) {
            Ok(p) => Some(p),
            Err(e) => {
                log::warn!("Failed to connect to NetworkManager, network information will not be available: {e}");
                return Self::default();
            }
        };

        Self {
            proxy,

            connections: Vec::new(),

            stats_cache: HashMap::new(),
            device_name_cache: HashMap::new(),
        }
    }

    fn refresh(&mut self) {
        let proxy = match &self.proxy {
            Some(p) => p,
            None => return,
        };

        let system_bus = match system_bus() {
            Some(c) => c,
            None => {
                log::warn!(
                    "Failed to connect to system bus, network information will not be available"
                );
                return;
            }
        };

        self.connections.clear();

        let rt = async_runtime();

        for if_name in InterfaceIter::new() {
            if if_name.starts_with("lo") {
                continue;
            }

            let iface = if_name.clone();
            let device_path = match sync!(rt, proxy.get_device_by_ip_iface(&iface)) {
                Ok(p) => p,
                Err(e) => {
                    log::warn!("Failed to get device path for {if_name}: {e}");
                    continue;
                }
            };

            let device_proxy = match sync!(
                rt,
                zbus::Proxy::new(
                    system_bus,
                    "org.freedesktop.NetworkManager",
                    device_path,
                    "org.freedesktop.NetworkManager.Device"
                )
            ) {
                Ok(p) => p,
                Err(e) => {
                    log::warn!("Failed to get device proxy for {if_name}: {e}");
                    continue;
                }
            };

            let syspath = match sync!(rt, device_proxy.get_property::<String>("Udi")) {
                Ok(p) => p,
                Err(e) => {
                    log::warn!("Failed to get syspath for {if_name}: {e}");
                    continue;
                }
            };

            let hw_address = match sync!(rt, device_proxy.get_property::<String>("HwAddress")) {
                Ok(p) => p,
                Err(e) => {
                    log::warn!("Failed to get syspath for {if_name}: {e}");
                    continue;
                }
            };

            let device_name = device_name(&mut self.device_name_cache, &syspath);
            let connection_kind = util::connection_kind(&if_name);

            let wireless_connection = if connection_kind == ConnectionKind::Wireless {
                wireless::wireless_connection(&if_name, rt, &device_proxy)
            } else {
                None
            };

            let (tx_bytes, rx_bytes) = stats::bytes_transfered(&if_name);
            let (tx_total_bytes, rx_total_bytes) = (tx_bytes.unwrap_or(0), rx_bytes.unwrap_or(0));

            let (tx_rate_bytes_ps, rx_rate_bytes_ps) = stats::transfer_rates(
                &if_name,
                &mut self.stats_cache,
                tx_total_bytes,
                rx_total_bytes,
            );

            let max_speed_bytes_ps = stats::max_speed(&if_name);

            let ipv4_address = ip::ipv4_address(&if_name, rt, system_bus, &device_proxy);
            let ipv6_address = ip::ipv6_address(&if_name, rt, system_bus, &device_proxy);

            self.connections.push(Connection {
                id: if_name.to_string(),
                device_name,
                kind: connection_kind as i32,
                wireless_connection,
                tx_rate_bytes_ps,
                tx_total_bytes,
                rx_rate_bytes_ps,
                rx_total_bytes,
                max_speed_bytes_ps,
                hw_address,
                ipv4_address,
                ipv6_address,
            });
        }
    }

    fn cached_entries(&self) -> &[Connection] {
        &self.connections
    }
}
