// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements.  See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License.  You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

//! Skywalking report instance properties items.

use crate::proto::v3::{InstanceProperties, KeyStringValuePair};
use std::{collections::HashMap, process, sync::LazyLock};
use systemstat::{IpAddr, Platform, System};

static IPS: LazyLock<Vec<String>> = LazyLock::new(|| {
    System::new()
        .networks()
        .ok()
        .map(|networks| {
            networks
                .values()
                .filter(|network| {
                    network.name != "lo"
                        && !network.name.starts_with("docker")
                        && !network.name.starts_with("br-")
                })
                .flat_map(|network| {
                    network.addrs.iter().filter_map(|addr| match addr.addr {
                        IpAddr::V4(addr) => Some(addr.to_string()),
                        _ => None,
                    })
                })
                .collect()
        })
        .unwrap_or_default()
});

static HOST_NAME: LazyLock<Option<String>> = LazyLock::new(|| {
    hostname::get()
        .ok()
        .and_then(|hostname| hostname.into_string().ok())
});

const OS_NAME: Option<&str> = if cfg!(target_os = "linux") {
    Some("Linux")
} else if cfg!(target_os = "windows") {
    Some("Windows")
} else if cfg!(target_os = "macos") {
    Some("Mac OS X")
} else {
    None
};

/// Builder of [InstanceProperties].
#[derive(Debug, Default)]
pub struct Properties {
    inner: HashMap<String, Vec<String>>,
}

impl Properties {
    /// Instance properties key of host name.
    pub const KEY_HOST_NAME: &'static str = "hostname";
    /// Instance properties key of ipv4.
    pub const KEY_IPV4: &'static str = "ipv4";
    /// Instance properties key of programming language.
    pub const KEY_LANGUAGE: &'static str = "language";
    /// Instance properties key of os name.
    pub const KEY_OS_NAME: &'static str = "OS Name";
    /// Instance properties key of process number.
    pub const KEY_PROCESS_NO: &'static str = "Process No.";
}

impl Properties {
    /// New empty properties.
    #[inline]
    pub fn new() -> Self {
        Default::default()
    }

    /// Insert key value pair, will not overwrite the original, because multiple
    /// values of the same key can exist.
    pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) {
        self.inner.entry(key.into()).or_default().push(value.into());
    }

    /// Overwrite the values, whether there are multiple.
    pub fn update(&mut self, key: &str, value: impl Into<String>) {
        if let Some(values) = self.inner.get_mut(key) {
            *values = vec![value.into()];
        }
    }

    /// Remove all values associated the key.
    pub fn remove(&mut self, key: &str) {
        self.inner.remove(key);
    }

    /// Insert the OS system info, such as os name, host name, etc.
    pub fn insert_os_info(&mut self) {
        for (key, value) in build_os_info() {
            self.insert(key, value);
        }
    }

    pub(crate) fn convert_to_instance_properties(
        self,
        service_name: String,
        instance_name: String,
    ) -> InstanceProperties {
        let mut properties = Vec::new();
        for (key, values) in self.inner {
            for value in values {
                properties.push(KeyStringValuePair {
                    key: key.clone(),
                    value,
                });
            }
        }

        InstanceProperties {
            service: service_name,
            service_instance: instance_name,
            properties,
            layer: Default::default(),
        }
    }
}

fn build_os_info() -> Vec<(String, String)> {
    let mut items = Vec::new();

    if let Some(os_name) = OS_NAME.as_ref() {
        items.push((Properties::KEY_OS_NAME.to_string(), os_name.to_string()));
    }

    if let Some(host_name) = HOST_NAME.as_ref() {
        items.push((Properties::KEY_HOST_NAME.to_string(), host_name.clone()));
    }

    for ip in IPS.iter() {
        items.push((Properties::KEY_IPV4.to_string(), ip.to_string()));
    }

    items.push((
        Properties::KEY_PROCESS_NO.to_string(),
        process::id().to_string(),
    ));

    items.push((Properties::KEY_LANGUAGE.to_string(), "rust".to_string()));

    items
}
