use chrono::{DateTime, TimeDelta, Utc};
use std::sync::OnceLock;
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum OsPlatform {
    Linux,
    MacOs,
    Windows,
    Unknown,
}

impl OsPlatform {
    pub fn get() -> Self {
        match std::env::consts::OS {
            "linux" => Self::Linux,
            "windows" => Self::Windows,
            "macos" | "apple" => Self::MacOs,
            _ => Self::Unknown,
        }
    }

    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Linux => "linux",
            Self::Windows => "windows",
            Self::MacOs => "macos",
            Self::Unknown => "unknown",
        }
    }

    pub fn get_str() -> &'static str {
        Self::get().as_str()
    }
}

#[derive(Clone, Debug)]
pub struct AppInfo {
    pub version: String,
    pub os_platform: OsPlatform,
    pub os_version: String,
    pub build_timestamp: fn() -> DateTime<Utc>,
    pub git_commit_id: fn() -> Option<String>,
    pub git_commit_short_id: fn() -> Option<String>,
    pub git_commit_timestamp: fn() -> Option<DateTime<Utc>>,
}

impl AppInfo {
    pub fn new(
        version: &str,
        build_timestamp: fn() -> DateTime<Utc>,
        git_commit_id: fn() -> Option<String>,
        git_commit_short_id: fn() -> Option<String>,
        git_commit_timestamp: fn() -> Option<DateTime<Utc>>,
    ) -> Self {
        let os_info = os_info::get();
        let os_version = Some(os_info.version().to_string())
            .map(|s| s.trim().to_string())
            .filter(|s| !s.is_empty())
            .unwrap_or_else(|| "unknown".to_string());

        Self {
            version: version.to_string(),
            os_version,
            build_timestamp,
            git_commit_id,
            git_commit_short_id,
            git_commit_timestamp,
            os_platform: OsPlatform::get(),
        }
    }

    pub fn print_app_info(&self) -> String {
        let build_timestamp = (self.build_timestamp)()
            .format("%Y-%m-%d %H:%M:%S UTC")
            .to_string();
        let git_commit_short_id =
            (self.git_commit_short_id)().unwrap_or_else(|| "unknown".to_string());
        let git_commit_id = (self.git_commit_id)().unwrap_or_else(|| "unknown".to_string());
        let git_commit_timestamp = (self.git_commit_timestamp)()
            .map(|timestamp| timestamp.format("%Y-%m-%d %H:%M:%S UTC").to_string())
            .unwrap_or_else(|| "unknown".to_string());

        let rows = [
            ("Version", self.version.as_str()),
            ("Platform", self.os_platform.as_str()),
            ("OS Version", self.os_version.as_str()),
            ("Build Time", build_timestamp.as_str()),
            ("Git Commit", git_commit_short_id.as_str()),
            ("Git Commit Full", git_commit_id.as_str()),
            ("Git Commit Time", git_commit_timestamp.as_str()),
        ];

        let label_width = 15;
        let value_width = rows
            .iter()
            .map(|(_, value)| value.len())
            .max()
            .unwrap_or(42)
            .max(42);
        let border = format!(
            "+-{:-<label_width$}-+-{:-<value_width$}-+",
            "",
            "",
            label_width = label_width,
            value_width = value_width
        );

        let mut output = String::new();
        output.push_str(&border);
        output.push('\n');
        output.push_str(&format!(
            "| {:^width$} |\n",
            "Application Information",
            width = label_width + value_width + 3
        ));
        output.push_str(&border);
        output.push('\n');

        for (label, value) in rows {
            output.push_str(&format!(
                "| {:<label_width$} | {:<value_width$} |\n",
                label,
                value,
                label_width = label_width,
                value_width = value_width
            ));
        }

        output.push_str(&border);
        output
    }
}

pub static APP_UP_TIME_INSTANCE: OnceLock<AppUpTime> = OnceLock::new();

// Application up time
pub struct AppUpTime {
    time: DateTime<Utc>,
}

pub fn set_app_up_time() {
    APP_UP_TIME_INSTANCE
        .set(AppUpTime::new())
        .unwrap_or_default();
}

impl AppUpTime {
    pub fn new() -> Self {
        Self { time: Utc::now() }
    }

    pub fn get_startup_time(&self) -> DateTime<Utc> {
        self.time
    }

    pub fn time_delta_since(&self) -> TimeDelta {
        let now = Utc::now();
        now.signed_duration_since(self.time)
    }
}
