// SPDX-License-Identifier: Apache-2.0

use rtnetlink::packet_route::link::InfoData;
use rtnetlink::packet_utils::nla::Nla;
use serde::{Deserialize, Serialize};

use crate::{
    netlink::{parse_as_u32, parse_as_u8},
    NisporError,
};

const IFF_TUN: u8 = 1;
const IFF_TAP: u8 = 2;

const IFLA_TUN_OWNER: u16 = 1;
const IFLA_TUN_GROUP: u16 = 2;
const IFLA_TUN_TYPE: u16 = 3;
const IFLA_TUN_PI: u16 = 4;
const IFLA_TUN_VNET_HDR: u16 = 5;
const IFLA_TUN_PERSIST: u16 = 6;
const IFLA_TUN_MULTI_QUEUE: u16 = 7;
const IFLA_TUN_NUM_QUEUES: u16 = 8;
const IFLA_TUN_NUM_DISABLED_QUEUES: u16 = 9;

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)]
#[non_exhaustive]
pub struct TunInfo {
    pub mode: TunMode,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub owner: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub group: Option<u32>,
    pub pi: bool,
    pub vnet_hdr: bool,
    pub multi_queue: bool,
    pub persist: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub num_queues: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub num_disabled_queues: Option<u32>,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum TunMode {
    Tun,
    Tap,
    Unknown,
}

impl Default for TunMode {
    fn default() -> Self {
        TunMode::Unknown
    }
}

impl From<u8> for TunMode {
    fn from(d: u8) -> Self {
        match d {
            IFF_TUN => TunMode::Tun,
            IFF_TAP => TunMode::Tap,
            _ => {
                log::warn!("Unhandled TUN mode {}", d);
                TunMode::Unknown
            }
        }
    }
}

pub(crate) fn get_tun_info(data: &InfoData) -> Result<TunInfo, NisporError> {
    let mut tun_info = TunInfo::default();
    if let InfoData::Tun(nlas) = data {
        for nla in nlas {
            let mut payload = vec![0; nla.value_len()];
            nla.emit_value(&mut payload);
            let payload = payload.as_slice();
            match nla.kind() {
                IFLA_TUN_OWNER => {
                    tun_info.owner = Some(parse_as_u32(payload)?);
                }
                IFLA_TUN_GROUP => {
                    tun_info.group = Some(parse_as_u32(payload)?);
                }
                IFLA_TUN_TYPE => {
                    tun_info.mode = parse_as_u8(payload)?.into();
                }
                IFLA_TUN_PI => {
                    tun_info.pi = parse_as_u8(payload)? > 0;
                }
                IFLA_TUN_VNET_HDR => {
                    tun_info.vnet_hdr = parse_as_u8(payload)? > 0;
                }
                IFLA_TUN_PERSIST => {
                    tun_info.persist = parse_as_u8(payload)? > 0;
                }
                IFLA_TUN_MULTI_QUEUE => {
                    tun_info.multi_queue = parse_as_u8(payload)? > 0;
                }
                IFLA_TUN_NUM_QUEUES => {
                    tun_info.num_queues = Some(parse_as_u32(payload)?);
                }
                IFLA_TUN_NUM_DISABLED_QUEUES => {
                    tun_info.num_disabled_queues = Some(parse_as_u32(payload)?);
                }
                _ => {
                    log::warn!(
                        "Unhandled TUN NLA {} {:?}",
                        nla.kind(),
                        payload
                    );
                }
            }
        }
    }
    Ok(tun_info)
}
