// Copyright 2024 RustFS Team
//
// Licensed 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.

//! System information display module.
//!
//! This module provides the `--info` command functionality for querying and displaying
//! various system information including system basics, runtime stats, build info,
//! configuration, and dependencies.

use super::{InfoOpts, InfoType};
use crate::runtime_sources::current_buffer_config;
use crate::version::build;
use rustfs_credentials::Masked;
use serde::Serialize;
use std::fmt;

/// CPU information
#[derive(Serialize)]
struct CpuInfo {
    /// Number of logical CPU cores
    core_count: usize,
    /// CPU brand/vendor name
    brand: String,
    /// CPU frequency in MHz
    frequency_mhz: u64,
    /// CPU usage percentage (0-100)
    usage_percent: f64,
}

impl CpuInfo {
    fn collect(sys: &sysinfo::System) -> Self {
        let core_count = sys.cpus().len();
        let brand = sys
            .cpus()
            .first()
            .map(|c| c.brand().to_string())
            .unwrap_or_else(|| "Unknown".to_string());
        let frequency_mhz = sys.cpus().first().map(|c| c.frequency()).unwrap_or(0);

        // Calculate average CPU usage
        let usage_percent = if core_count > 0 {
            sys.cpus().iter().map(|c| c.cpu_usage() as f64).sum::<f64>() / core_count as f64
        } else {
            0.0
        };

        Self {
            core_count,
            brand,
            frequency_mhz,
            usage_percent,
        }
    }
}

/// Memory information
#[derive(Serialize)]
struct MemoryInfo {
    /// Total system memory in bytes
    total_bytes: u64,
    /// Used memory in bytes
    used_bytes: u64,
    /// Available/free memory in bytes
    available_bytes: u64,
    /// Total swap memory in bytes
    total_swap_bytes: u64,
    /// Used swap memory in bytes
    used_swap_bytes: u64,
    /// Memory usage percentage
    usage_percent: f64,
}

impl MemoryInfo {
    fn collect(sys: &sysinfo::System) -> Self {
        let total_bytes = sys.total_memory();
        let used_bytes = sys.used_memory();
        let available_bytes = sys.available_memory();
        let total_swap_bytes = sys.total_swap();
        let used_swap_bytes = sys.used_swap();

        let usage_percent = if total_bytes > 0 {
            (used_bytes as f64 / total_bytes as f64) * 100.0
        } else {
            0.0
        };

        Self {
            total_bytes,
            used_bytes,
            available_bytes,
            total_swap_bytes,
            used_swap_bytes,
            usage_percent,
        }
    }

    fn format_bytes(bytes: u64) -> String {
        const KB: u64 = 1024;
        const MB: u64 = KB * 1024;
        const GB: u64 = MB * 1024;
        const TB: u64 = GB * 1024;

        if bytes >= TB {
            format!("{:.2} TB", bytes as f64 / TB as f64)
        } else if bytes >= GB {
            format!("{:.2} GB", bytes as f64 / GB as f64)
        } else if bytes >= MB {
            format!("{:.2} MB", bytes as f64 / MB as f64)
        } else if bytes >= KB {
            format!("{:.2} KB", bytes as f64 / KB as f64)
        } else {
            format!("{} B", bytes)
        }
    }
}

/// Disk information
#[derive(Serialize)]
struct DiskInfo {
    /// Disk mount point
    mount_point: String,
    /// Disk name/device
    name: String,
    /// File system type
    file_system: String,
    /// Total space in bytes
    total_bytes: u64,
    /// Used space in bytes
    used_bytes: u64,
    /// Available space in bytes
    available_bytes: u64,
    /// Usage percentage
    usage_percent: f64,
    /// Is this a removable disk
    is_removable: bool,
}

impl DiskInfo {
    fn collect_all() -> Vec<Self> {
        let disks = sysinfo::Disks::new_with_refreshed_list();
        disks
            .iter()
            .map(|disk| {
                let total_bytes = disk.total_space();
                let available_bytes = disk.available_space();
                let used_bytes = total_bytes.saturating_sub(available_bytes);
                let usage_percent = if total_bytes > 0 {
                    (used_bytes as f64 / total_bytes as f64) * 100.0
                } else {
                    0.0
                };

                Self {
                    mount_point: disk.mount_point().to_string_lossy().to_string(),
                    name: disk.name().to_string_lossy().to_string(),
                    file_system: format!("{:?}", disk.file_system()),
                    total_bytes,
                    used_bytes,
                    available_bytes,
                    usage_percent,
                    is_removable: disk.is_removable(),
                }
            })
            .collect()
    }
}

/// Service disk information (disk where the service data is stored)
#[derive(Serialize)]
struct ServiceDiskInfo {
    /// Disk mount point
    mount_point: String,
    /// Disk name/device
    name: String,
    /// File system type
    file_system: String,
    /// Total space in bytes
    total_bytes: u64,
    /// Used space in bytes
    used_bytes: u64,
    /// Available space in bytes
    available_bytes: u64,
    /// Usage percentage
    usage_percent: f64,
    /// Volume paths served by this disk
    volume_paths: Vec<String>,
}

impl ServiceDiskInfo {
    fn collect(volumes: &[String]) -> Option<Self> {
        // Find the disk that contains the first volume path
        let first_volume = volumes.first()?;
        let volume_path = std::path::Path::new(first_volume);

        // Find the disk that contains this volume
        let disks = sysinfo::Disks::new_with_refreshed_list();
        for disk in disks.iter() {
            let mount_point = disk.mount_point();
            if volume_path.starts_with(mount_point) {
                let total_bytes = disk.total_space();
                let available_bytes = disk.available_space();
                let used_bytes = total_bytes.saturating_sub(available_bytes);
                let usage_percent = if total_bytes > 0 {
                    (used_bytes as f64 / total_bytes as f64) * 100.0
                } else {
                    0.0
                };

                // Find all volumes on this disk
                let volume_paths: Vec<String> = volumes
                    .iter()
                    .filter(|v| std::path::Path::new(v).starts_with(mount_point))
                    .cloned()
                    .collect();

                return Some(Self {
                    mount_point: mount_point.to_string_lossy().to_string(),
                    name: disk.name().to_string_lossy().to_string(),
                    file_system: format!("{:?}", disk.file_system()),
                    total_bytes,
                    used_bytes,
                    available_bytes,
                    usage_percent,
                    volume_paths,
                });
            }
        }

        None
    }

    fn format(&self) -> String {
        let volumes_str = if self.volume_paths.is_empty() {
            "(none)".to_string()
        } else {
            self.volume_paths.join(", ")
        };

        format!(
            "## Service Disk Information\n\n\
             | Property | Value |\n\
             |----------|-------|\n\
             | Disk | {} |\n\
             | Mount Point | {} |\n\
             | File System | {} |\n\
             | Total Space | {} |\n\
             | Used Space | {} ({:.1}%) |\n\
             | Available Space | {} |\n\
             | Served Volumes | {} |",
            self.name,
            self.mount_point,
            self.file_system,
            MemoryInfo::format_bytes(self.total_bytes),
            MemoryInfo::format_bytes(self.used_bytes),
            self.usage_percent,
            MemoryInfo::format_bytes(self.available_bytes),
            volumes_str
        )
    }
}

/// System basic information
#[derive(Serialize)]
struct SystemInfo {
    os_type: String,
    os_version: String,
    architecture: String,
    hostname: String,
    kernel_version: String,
    /// CPU information
    cpu: CpuInfo,
    /// Memory information
    memory: MemoryInfo,
    /// All disk information
    disks: Vec<DiskInfo>,
}

impl SystemInfo {
    fn collect() -> Self {
        // Create system info collector
        let mut sys = sysinfo::System::new_all();
        sys.refresh_all();
        sys.refresh_cpu_all();

        Self {
            os_type: sysinfo::System::distribution_id(),
            os_version: sysinfo::System::long_os_version().unwrap_or_else(|| "Unknown".to_string()),
            architecture: std::env::consts::ARCH.to_string(),
            hostname: sysinfo::System::host_name().unwrap_or_else(|| "Unknown".to_string()),
            kernel_version: sysinfo::System::kernel_long_version(),
            cpu: CpuInfo::collect(&sys),
            memory: MemoryInfo::collect(&sys),
            disks: DiskInfo::collect_all(),
        }
    }

    fn format(&self) -> String {
        let mut output = String::new();

        // System Information Table
        output.push_str("## System Information\n\n");
        output.push_str("| Property | Value |\n");
        output.push_str("|----------|-------|\n");
        output.push_str(&format!("| OS | {} |\n", self.os_type));
        output.push_str(&format!("| OS Version | {} |\n", self.os_version));
        output.push_str(&format!("| Architecture | {} |\n", self.architecture));
        output.push_str(&format!("| Hostname | {} |\n", self.hostname));
        output.push_str(&format!("| Kernel Version | {} |\n", self.kernel_version));

        // CPU Information Table
        output.push_str("\n## CPU Information\n\n");
        output.push_str("| Property | Value |\n");
        output.push_str("|----------|-------|\n");
        output.push_str(&format!("| Cores | {} |\n", self.cpu.core_count));
        output.push_str(&format!("| Brand | {} |\n", self.cpu.brand));
        output.push_str(&format!("| Frequency | {} MHz |\n", self.cpu.frequency_mhz));
        output.push_str(&format!("| Usage | {:.1}% |\n", self.cpu.usage_percent));

        // Memory Information Table
        output.push_str("\n## Memory Information\n\n");
        output.push_str("| Property | Value |\n");
        output.push_str("|----------|-------|\n");
        output.push_str(&format!("| Total | {} |\n", MemoryInfo::format_bytes(self.memory.total_bytes)));
        output.push_str(&format!(
            "| Used | {} ({:.1}%) |\n",
            MemoryInfo::format_bytes(self.memory.used_bytes),
            self.memory.usage_percent
        ));
        output.push_str(&format!("| Available | {} |\n", MemoryInfo::format_bytes(self.memory.available_bytes)));
        output.push_str(&format!("| Total Swap | {} |\n", MemoryInfo::format_bytes(self.memory.total_swap_bytes)));
        output.push_str(&format!("| Used Swap | {} |\n", MemoryInfo::format_bytes(self.memory.used_swap_bytes)));

        // Disk Information Table
        output.push_str("\n## Disk Information\n\n");
        if self.disks.is_empty() {
            output.push_str("*No disks found*\n");
        } else {
            output.push_str("| Name | Mount Point | Type | Total | Used | Available | Usage | Removable |\n");
            output.push_str("|------|-------------|------|-------|------|-----------|-------|----------|\n");
            for disk in &self.disks {
                output.push_str(&format!(
                    "| {} | {} | {} | {} | {} | {} | {:.1}% | {} |\n",
                    disk.name,
                    disk.mount_point,
                    disk.file_system,
                    MemoryInfo::format_bytes(disk.total_bytes),
                    MemoryInfo::format_bytes(disk.used_bytes),
                    MemoryInfo::format_bytes(disk.available_bytes),
                    disk.usage_percent,
                    disk.is_removable
                ));
            }
        }

        output
    }
}
impl fmt::Display for SystemInfo {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.format())
    }
}

/// Runtime information
#[derive(Serialize)]
struct RuntimeInfo {
    process_id: u32,
    memory_usage_mb: f64,
    cpu_usage_percent: f64,
    thread_count: usize,
}

impl RuntimeInfo {
    fn collect() -> Self {
        let (process_id, memory_usage_mb, cpu_usage_percent) = if let Ok(pid) = sysinfo::get_current_pid() {
            let mut sys = sysinfo::System::new();
            sys.refresh_processes_specifics(
                sysinfo::ProcessesToUpdate::Some(&[pid]),
                true,
                sysinfo::ProcessRefreshKind::everything(),
            );

            let process = sys.process(pid);
            let (memory_usage_mb, cpu_usage_percent) = if let Some(p) = process {
                let memory = p.memory() as f64 / 1024.0 / 1024.0; // Convert to MB
                let cpu = p.cpu_usage() as f64;
                (memory, cpu)
            } else {
                (0.0, 0.0)
            };

            (pid.as_u32(), memory_usage_mb, cpu_usage_percent)
        } else {
            // Failed to retrieve current PID; degrade gracefully by
            // skipping per-process stats and using sentinel values.
            (0, 0.0, 0.0)
        };

        // // Get available CPU parallelism (roughly, logical cores available to the process)
        let cpu_parallelism = std::thread::available_parallelism().map(|p| p.get()).unwrap_or(1);

        Self {
            process_id,
            memory_usage_mb,
            cpu_usage_percent,
            thread_count: cpu_parallelism,
        }
    }

    fn format(&self) -> String {
        let pid_display = if self.process_id == 0 {
            "unknown".to_string()
        } else {
            self.process_id.to_string()
        };

        format!(
            "## Runtime Information\n\n\
             | Property | Value |\n\
             |----------|-------|\n\
             | Process ID | {} |\n\
             | Memory Usage | {:.2} MB |\n\
             | CPU Usage | {:.2}% |\n\
             | CPU Parallelism | {} |",
            pid_display, self.memory_usage_mb, self.cpu_usage_percent, self.thread_count
        )
    }
}
impl fmt::Display for RuntimeInfo {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.format())
    }
}

/// Build information (using shadow-rs generated constants)
struct BuildInfo;

impl BuildInfo {
    fn format() -> String {
        format!(
            "## Build Information\n\n\
             | Property | Value |\n\
             |----------|-------|\n\
             | Version | {} |\n\
             | Build Time | {} |\n\
             | Build Profile | {} |\n\
             | Build OS | {} |\n\
             | Rust Version | {} |\n\
             | Git Branch | {} |\n\
             | Git Commit | {} |\n\
             | Git Tag | {} |\n\
             | Git Status | {} |",
            build::PKG_VERSION,
            build::BUILD_TIME,
            build::BUILD_RUST_CHANNEL,
            build::BUILD_OS,
            build::RUST_VERSION,
            build::BRANCH,
            build::COMMIT_HASH,
            build::TAG,
            build::GIT_STATUS_FILE
        )
    }
}

impl fmt::Display for BuildInfo {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", Self::format())
    }
}

// ============================================================================
// JSON Output Structures
// ============================================================================

/// Build information for JSON output
#[derive(Serialize)]
struct BuildInfoJson {
    version: &'static str,
    build_time: &'static str,
    build_profile: &'static str,
    build_os: &'static str,
    rust_version: &'static str,
    git_branch: &'static str,
    git_commit: &'static str,
    git_tag: &'static str,
    git_status: &'static str,
}

impl BuildInfo {
    fn collect_json() -> BuildInfoJson {
        BuildInfoJson {
            version: build::PKG_VERSION,
            build_time: build::BUILD_TIME,
            build_profile: build::BUILD_RUST_CHANNEL,
            build_os: build::BUILD_OS,
            rust_version: build::RUST_VERSION,
            git_branch: build::BRANCH,
            git_commit: build::COMMIT_HASH,
            git_tag: build::TAG,
            git_status: build::GIT_STATUS_FILE,
        }
    }
}

/// Configuration information for JSON output
#[derive(Serialize)]
struct ConfigInfoJson {
    address: String,
    console_enable: bool,
    console_address: String,
    region: Option<String>,
    access_key: String,
    obs_endpoint: String,
    tls_path: Option<String>,
    kms_enable: bool,
    kms_backend: String,
    buffer_profile: String,
    workload_profile: Option<WorkloadProfileJson>,
}

/// Workload profile information for JSON output
#[derive(Serialize)]
struct WorkloadProfileJson {
    name: String,
    buffer_min_size: usize,
    buffer_max_size: usize,
    default_unknown: usize,
}

fn collect_config_info_json() -> ConfigInfoJson {
    let snapshot = super::get_config_snapshot_for_display();

    // Get workload profile info
    let workload_profile = {
        use super::workload_profiles::is_buffer_profile_enabled;
        if is_buffer_profile_enabled() {
            let config = current_buffer_config().unwrap_or_default();
            let profile = config.workload_profile();
            let buffer_config = profile.config();
            Some(WorkloadProfileJson {
                name: config.workload_name(),
                buffer_min_size: buffer_config.min_size,
                buffer_max_size: buffer_config.max_size,
                default_unknown: buffer_config.default_unknown,
            })
        } else {
            None
        }
    };

    ConfigInfoJson {
        address: snapshot.address.clone(),
        console_enable: snapshot.console_enable,
        console_address: snapshot.console_address.clone(),
        region: snapshot.region.clone(),
        access_key: snapshot.access_key.clone(),
        obs_endpoint: snapshot.obs_endpoint.clone(),
        tls_path: snapshot.tls_path.clone(),
        kms_enable: snapshot.kms_enable,
        kms_backend: snapshot.kms_backend.clone(),
        buffer_profile: snapshot.buffer_profile.clone(),
        workload_profile,
    }
}

/// Feature information for JSON output
#[derive(Serialize)]
struct FeatureInfoJson {
    name: &'static str,
    enabled: bool,
    description: &'static str,
}

struct FeatureSpec {
    name: &'static str,
    enabled: bool,
    description: &'static str,
    dependencies: &'static str,
    default_enabled: bool,
}

fn feature_specs() -> &'static [FeatureSpec] {
    &[
        FeatureSpec {
            name: "default",
            enabled: cfg!(feature = "default"),
            description: "Default feature set",
            dependencies: "ftps + webdav",
            default_enabled: true,
        },
        FeatureSpec {
            name: "metrics-gpu",
            enabled: cfg!(feature = "metrics-gpu"),
            description: "GPU metrics support",
            dependencies: "rustfs-obs/gpu",
            default_enabled: false,
        },
        FeatureSpec {
            name: "ftps",
            enabled: cfg!(feature = "ftps"),
            description: "FTPS protocol support",
            dependencies: "rustfs-protocols/ftps",
            default_enabled: true,
        },
        FeatureSpec {
            name: "swift",
            enabled: cfg!(feature = "swift"),
            description: "OpenStack Swift protocol support",
            dependencies: "rustfs-protocols/swift",
            default_enabled: false,
        },
        FeatureSpec {
            name: "webdav",
            enabled: cfg!(feature = "webdav"),
            description: "WebDAV protocol support",
            dependencies: "rustfs-protocols/webdav",
            default_enabled: true,
        },
        FeatureSpec {
            name: "sftp",
            enabled: cfg!(feature = "sftp"),
            description: "SFTP protocol support",
            dependencies: "rustfs-protocols/sftp",
            default_enabled: false,
        },
        FeatureSpec {
            name: "license",
            enabled: cfg!(feature = "license"),
            description: "License validation",
            dependencies: "(none)",
            default_enabled: false,
        },
        FeatureSpec {
            name: "io-scheduler-debug",
            enabled: cfg!(feature = "io-scheduler-debug"),
            description: "Enable debug information in I/O scheduler",
            dependencies: "(none)",
            default_enabled: false,
        },
        FeatureSpec {
            name: "tracing-chunk-debug",
            enabled: cfg!(feature = "tracing-chunk-debug"),
            description: "Per-chunk data-plane tracing",
            dependencies: "(none)",
            default_enabled: false,
        },
        FeatureSpec {
            name: "full",
            enabled: cfg!(feature = "full"),
            description: "Full protocol and observability bundle",
            dependencies: "metrics-gpu + ftps + swift + webdav + sftp + pyroscope",
            default_enabled: false,
        },
        FeatureSpec {
            name: "e2e-test-hooks",
            enabled: cfg!(feature = "e2e-test-hooks"),
            description: "End-to-end test hooks",
            dependencies: "(none)",
            default_enabled: false,
        },
        FeatureSpec {
            name: "connect-e2e-short-credentials",
            enabled: cfg!(feature = "connect-e2e-short-credentials"),
            description: "Short-lived Connect credentials for debug E2E builds",
            dependencies: "(none)",
            default_enabled: false,
        },
        FeatureSpec {
            name: "offline-enrollment-e2e-root",
            enabled: cfg!(feature = "offline-enrollment-e2e-root"),
            description: "Dedicated offline enrollment E2E root",
            dependencies: "(none)",
            default_enabled: false,
        },
        FeatureSpec {
            name: "rio-v2",
            enabled: cfg!(feature = "rio-v2"),
            description: "RIO v2 storage path support",
            dependencies: "rustfs-ecstore/rio-v2",
            default_enabled: false,
        },
        FeatureSpec {
            name: "pyroscope",
            enabled: cfg!(feature = "pyroscope"),
            description: "Pyroscope profiling support",
            dependencies: "rustfs-obs/pyroscope",
            default_enabled: false,
        },
        FeatureSpec {
            name: "dial9",
            enabled: cfg!(feature = "dial9"),
            description: "Tokio runtime telemetry",
            dependencies: "rustfs-obs/dial9",
            default_enabled: false,
        },
        FeatureSpec {
            name: "hotpath",
            enabled: cfg!(feature = "hotpath"),
            description: "Hotpath instrumentation",
            dependencies: "hotpath + RustFS crate hotpath features",
            default_enabled: false,
        },
        FeatureSpec {
            name: "hotpath-alloc",
            enabled: cfg!(feature = "hotpath-alloc"),
            description: "Hotpath allocation diagnostics",
            dependencies: "hotpath + hotpath/hotpath-alloc + RustFS crate hotpath-alloc features",
            default_enabled: false,
        },
        FeatureSpec {
            name: "hotpath-cpu",
            enabled: cfg!(feature = "hotpath-cpu"),
            description: "Hotpath CPU attribution",
            dependencies: "hotpath + hotpath/hotpath-cpu + RustFS crate hotpath-cpu features",
            default_enabled: false,
        },
    ]
}

/// Dependency information for JSON output
#[derive(Serialize)]
struct DepsInfoJson {
    enabled_count: usize,
    total_count: usize,
    features: Vec<FeatureInfoJson>,
}

fn collect_deps_info_json() -> DepsInfoJson {
    let features: Vec<FeatureInfoJson> = feature_specs()
        .iter()
        .map(|feature| FeatureInfoJson {
            name: feature.name,
            enabled: feature.enabled,
            description: feature.description,
        })
        .collect();

    let enabled_count = features.iter().filter(|f| f.enabled).count();
    let total_count = features.len();

    DepsInfoJson {
        enabled_count,
        total_count,
        features,
    }
}

/// All information combined for JSON output
#[derive(Serialize)]
struct AllInfoJson {
    system: SystemInfo,
    runtime: RuntimeInfo,
    build: BuildInfoJson,
    config: ConfigInfoJson,
    deps: DepsInfoJson,
    #[serde(skip_serializing_if = "Option::is_none")]
    service_disk: Option<ServiceDiskInfo>,
}

/// Print info as JSON
fn print_info_json<T: Serialize>(info: &T) {
    match serde_json::to_string_pretty(info) {
        Ok(json) => println!("{}", json),
        Err(e) => eprintln!("Failed to serialize info to JSON: {}", e),
    }
}

/// Execute info command with JSON output
fn execute_info_json(info_type: Option<InfoType>, volumes: &[String]) {
    match info_type {
        None => {
            // Output all info as nested JSON object
            let all_info = AllInfoJson {
                system: SystemInfo::collect(),
                runtime: RuntimeInfo::collect(),
                build: BuildInfo::collect_json(),
                config: collect_config_info_json(),
                deps: collect_deps_info_json(),
                service_disk: ServiceDiskInfo::collect(volumes),
            };
            print_info_json(&all_info);
        }
        Some(InfoType::System) => {
            print_info_json(&SystemInfo::collect());
        }
        Some(InfoType::Runtime) => {
            print_info_json(&RuntimeInfo::collect());
        }
        Some(InfoType::Build) => {
            print_info_json(&BuildInfo::collect_json());
        }
        Some(InfoType::Config) => {
            print_info_json(&collect_config_info_json());
        }
        Some(InfoType::Deps) => {
            print_info_json(&collect_deps_info_json());
        }
    }
}

/// Configuration information display
fn format_config_info() -> String {
    // Get config snapshot for display (from global if initialized, otherwise from env)
    let snapshot = super::get_config_snapshot_for_display();

    // Get workload profile info
    let workload_info = get_workload_profile_info();

    // Mask the access key for display
    let masked_access_key = &Masked(Some(&snapshot.access_key));

    let obs_endpoint = if snapshot.obs_endpoint.is_empty() {
        "(not set)"
    } else {
        &snapshot.obs_endpoint
    };
    let protocol_info = format_protocol_config_info();

    format!(
        "## Configuration Information\n\n\
         | Property | Value |\n\
         |----------|-------|\n\
         | Server Address | {} |\n\
         | Console Enable | {} |\n\
         | Console Address | {} |\n\
         | Region | {} |\n\
         | Access Key | {} |\n\
         | Secret Key | **** |\n\
         | OBS Endpoint | {} |\n\
         | TLS Path | {} |\n\
         | KMS Enabled | {} |\n\
         | KMS Backend | {} |\n\
         | Buffer Profile | {} |\n\
         {}\n\
         {}",
        snapshot.address,
        snapshot.console_enable,
        snapshot.console_address,
        snapshot.region.as_deref().unwrap_or("(not set)"),
        masked_access_key,
        obs_endpoint,
        snapshot.tls_path.as_deref().unwrap_or("(not set)"),
        snapshot.kms_enable,
        snapshot.kms_backend,
        snapshot.buffer_profile,
        workload_info,
        protocol_info
    )
}

fn format_protocol_config_info() -> String {
    const DEFAULT_FTPS_PASSIVE_PORTS: &str = "40000-50000";
    const DEFAULT_WEBDAV_MAX_BODY_SIZE: u64 = 5 * 1024 * 1024 * 1024;
    const DEFAULT_WEBDAV_REQUEST_TIMEOUT_SECS: u64 = 300;
    const DEFAULT_WEBDAV_MAX_CONNECTIONS: usize = 1024;

    let ftps_enable = rustfs_utils::get_env_bool(rustfs_config::ENV_FTPS_ENABLE, false);
    let ftps_address = rustfs_utils::get_env_str(rustfs_config::ENV_FTPS_ADDRESS, rustfs_config::DEFAULT_FTPS_ADDRESS);
    let ftps_tls_enabled = rustfs_utils::get_env_bool(rustfs_config::ENV_FTPS_TLS_ENABLED, true);
    let ftps_certs_dir =
        rustfs_utils::get_env_opt_str(rustfs_config::ENV_FTPS_CERTS_DIR).unwrap_or_else(|| "(not set)".to_string());
    let ftps_ca_file = rustfs_utils::get_env_opt_str(rustfs_config::ENV_FTPS_CA_FILE).unwrap_or_else(|| "(not set)".to_string());
    let ftps_passive_ports = rustfs_utils::get_env_opt_str(rustfs_config::ENV_FTPS_PASSIVE_PORTS)
        .unwrap_or_else(|| DEFAULT_FTPS_PASSIVE_PORTS.to_string());
    let ftps_external_ip =
        rustfs_utils::get_env_opt_str(rustfs_config::ENV_FTPS_EXTERNAL_IP).unwrap_or_else(|| "(not set)".to_string());

    let webdav_enable = rustfs_utils::get_env_bool(rustfs_config::ENV_WEBDAV_ENABLE, false);
    let webdav_address = rustfs_utils::get_env_str(rustfs_config::ENV_WEBDAV_ADDRESS, rustfs_config::DEFAULT_WEBDAV_ADDRESS);
    let webdav_tls_enabled = rustfs_utils::get_env_bool(rustfs_config::ENV_WEBDAV_TLS_ENABLED, true);
    let webdav_certs_dir =
        rustfs_utils::get_env_opt_str(rustfs_config::ENV_WEBDAV_CERTS_DIR).unwrap_or_else(|| "(not set)".to_string());
    let webdav_ca_file =
        rustfs_utils::get_env_opt_str(rustfs_config::ENV_WEBDAV_CA_FILE).unwrap_or_else(|| "(not set)".to_string());
    let webdav_max_body_size = rustfs_utils::get_env_u64(rustfs_config::ENV_WEBDAV_MAX_BODY_SIZE, DEFAULT_WEBDAV_MAX_BODY_SIZE);
    let webdav_request_timeout =
        rustfs_utils::get_env_u64(rustfs_config::ENV_WEBDAV_REQUEST_TIMEOUT, DEFAULT_WEBDAV_REQUEST_TIMEOUT_SECS);
    let webdav_max_connections =
        rustfs_utils::get_env_usize(rustfs_config::ENV_WEBDAV_MAX_CONNECTIONS, DEFAULT_WEBDAV_MAX_CONNECTIONS);

    format!(
        "| FTPS | --- |\n\
         | FTPS > Build Feature | {} |\n\
         | FTPS > Enabled (`{}`) | {} |\n\
         | FTPS > Address (`{}`) | {} |\n\
         | FTPS > TLS Enabled (`{}`) | {} |\n\
         | FTPS > Certs Dir (`{}`) | {} |\n\
         | FTPS > CA File (`{}`) | {} |\n\
         | FTPS > Passive Ports (`{}`) | {} |\n\
         | FTPS > External IP (`{}`) | {} |\n\
         | WebDAV | --- |\n\
         | WebDAV > Build Feature | {} |\n\
         | WebDAV > Enabled (`{}`) | {} |\n\
         | WebDAV > Address (`{}`) | {} |\n\
         | WebDAV > TLS Enabled (`{}`) | {} |\n\
         | WebDAV > Certs Dir (`{}`) | {} |\n\
         | WebDAV > CA File (`{}`) | {} |\n\
         | WebDAV > Max Body Size (`{}`) | {} bytes |\n\
         | WebDAV > Request Timeout (`{}`) | {} seconds |\n\
         | WebDAV > Max Connections (`{}`) | {} |",
        if cfg!(feature = "ftps") { "enabled" } else { "disabled" },
        rustfs_config::ENV_FTPS_ENABLE,
        ftps_enable,
        rustfs_config::ENV_FTPS_ADDRESS,
        ftps_address,
        rustfs_config::ENV_FTPS_TLS_ENABLED,
        ftps_tls_enabled,
        rustfs_config::ENV_FTPS_CERTS_DIR,
        ftps_certs_dir,
        rustfs_config::ENV_FTPS_CA_FILE,
        ftps_ca_file,
        rustfs_config::ENV_FTPS_PASSIVE_PORTS,
        ftps_passive_ports,
        rustfs_config::ENV_FTPS_EXTERNAL_IP,
        ftps_external_ip,
        if cfg!(feature = "webdav") { "enabled" } else { "disabled" },
        rustfs_config::ENV_WEBDAV_ENABLE,
        webdav_enable,
        rustfs_config::ENV_WEBDAV_ADDRESS,
        webdav_address,
        rustfs_config::ENV_WEBDAV_TLS_ENABLED,
        webdav_tls_enabled,
        rustfs_config::ENV_WEBDAV_CERTS_DIR,
        webdav_certs_dir,
        rustfs_config::ENV_WEBDAV_CA_FILE,
        webdav_ca_file,
        rustfs_config::ENV_WEBDAV_MAX_BODY_SIZE,
        webdav_max_body_size,
        rustfs_config::ENV_WEBDAV_REQUEST_TIMEOUT,
        webdav_request_timeout,
        rustfs_config::ENV_WEBDAV_MAX_CONNECTIONS,
        webdav_max_connections
    )
}

/// Get workload profile information from global buffer config
fn get_workload_profile_info() -> String {
    use super::workload_profiles::is_buffer_profile_enabled;

    if !is_buffer_profile_enabled() {
        return "| Workload Profile | (disabled) |".to_string();
    }

    let config = current_buffer_config().unwrap_or_default();
    let profile = config.workload_profile();
    let name = config.workload_name();
    let buffer_config = profile.config();

    format!(
        "| Workload Profile | {} |\n\
         | Buffer Min Size | {} bytes |\n\
         | Buffer Max Size | {} bytes |\n\
         | Default Unknown | {} bytes |",
        name, buffer_config.min_size, buffer_config.max_size, buffer_config.default_unknown
    )
}

/// Dependency information
fn format_deps_info() -> String {
    let features = feature_specs();

    let enabled_count = features.iter().filter(|feature| feature.enabled).count();

    let mut output = format!(
        "## Build Features\n\n\
         | Property | Value |\n\
         |----------|-------|\n\
         | Enabled Features | {}/{} |\n\n",
        enabled_count,
        features.len()
    );

    output.push_str("### Feature Status\n\n");
    output.push_str("| Feature | Status | Description |\n");
    output.push_str("|---------|--------|-------------|\n");
    for feature in features {
        let status = if feature.enabled { "✓" } else { "✗" };
        output.push_str(&format!("| {} | {} | {} |\n", feature.name, status, feature.description));
    }

    output.push_str("\n### Default Features\n\n");
    output.push_str("| Feature | Note |\n");
    output.push_str("|---------|------|\n");
    for feature in features.iter().filter(|feature| feature.default_enabled) {
        output.push_str(&format!("| {} | enabled by default |\n", feature.name));
    }

    output.push_str("\n### Feature Dependencies\n\n");
    output.push_str("| Feature | Dependencies |\n");
    output.push_str("|---------|-------------|\n");
    for feature in features {
        output.push_str(&format!("| {} | {} |\n", feature.name, feature.dependencies));
    }

    output
}

/// Execute the info command
pub fn execute_info(opts: &InfoOpts) {
    execute_info_with_volumes(opts, &[])
}

/// Execute info command with volume paths for service disk information
pub fn execute_info_with_volumes(opts: &InfoOpts, volumes: &[String]) {
    let info_type = if opts.all {
        None // None means display all
    } else {
        opts.info_type
    };

    // JSON output path
    if opts.json {
        execute_info_json(info_type, volumes);
        return;
    }

    // Markdown output path (default)
    match info_type {
        None => {
            // Display all information
            println!("{}", SystemInfo::collect());
            println!();
            println!("{}", RuntimeInfo::collect());
            println!();
            println!("{}", BuildInfo);
            println!();
            println!("{}", format_config_info());
            println!();
            // Display service disk info if volumes are configured
            if let Some(service_disk) = ServiceDiskInfo::collect(volumes) {
                println!("{}", service_disk.format());
                println!();
            }
            println!("{}", format_deps_info());
        }
        Some(InfoType::System) => {
            println!("{}", SystemInfo::collect());
        }
        Some(InfoType::Runtime) => {
            println!("{}", RuntimeInfo::collect());
        }
        Some(InfoType::Build) => {
            println!("{}", BuildInfo);
        }
        Some(InfoType::Config) => {
            println!("{}", format_config_info());
        }
        Some(InfoType::Deps) => {
            println!("{}", format_deps_info());
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_system_info_collect() {
        let info = SystemInfo::collect();
        assert!(!info.os_type.is_empty());
        assert!(!info.architecture.is_empty());
    }

    #[test]
    fn test_runtime_info_collect() {
        let info = RuntimeInfo::collect();
        assert!(info.process_id > 0);
    }

    #[test]
    fn test_collect_deps_info_json_matches_cargo_features() {
        let info = collect_deps_info_json();
        let feature_names: Vec<_> = info.features.iter().map(|feature| feature.name).collect();

        assert_eq!(info.total_count, 19);
        assert_eq!(info.features.len(), 19);
        assert!(feature_names.contains(&"default"));
        assert!(feature_names.contains(&"metrics-gpu"));
        assert!(feature_names.contains(&"sftp"));
        assert!(feature_names.contains(&"io-scheduler-debug"));
        assert!(feature_names.contains(&"tracing-chunk-debug"));
        assert!(feature_names.contains(&"e2e-test-hooks"));
        assert!(feature_names.contains(&"connect-e2e-short-credentials"));
        assert!(feature_names.contains(&"offline-enrollment-e2e-root"));
        assert!(feature_names.contains(&"rio-v2"));
        assert!(feature_names.contains(&"pyroscope"));
        assert!(feature_names.contains(&"dial9"));
        assert!(feature_names.contains(&"hotpath"));
        assert!(feature_names.contains(&"hotpath-alloc"));
        assert!(feature_names.contains(&"hotpath-cpu"));
        assert!(!feature_names.contains(&"manual-test-runners"));
        assert!(!feature_names.contains(&"metrics"));
        assert!(!feature_names.contains(&"direct-io"));
    }

    #[test]
    fn test_format_deps_info_matches_cargo_feature_output() {
        let output = format_deps_info();

        assert!(output.contains("| metrics-gpu |"));
        assert!(output.contains("| io-scheduler-debug |"));
        assert!(output.contains("| tracing-chunk-debug |"));
        assert!(output.contains("| sftp |"));
        assert!(output.contains("| rio-v2 |"));
        assert!(output.contains("| dial9 |"));
        assert!(output.contains("| hotpath-cpu |"));
        assert!(output.contains("| default | enabled by default |"));
        assert!(!output.contains("| manual-test-runners |"));
        assert!(output.contains("| ftps | enabled by default |"));
        assert!(output.contains("| webdav | enabled by default |"));
        assert!(output.contains("| full | metrics-gpu + ftps + swift + webdav + sftp + pyroscope |"));
        assert!(!output.contains("| direct-io |"));
    }

    #[test]
    fn test_format_config_info_includes_ftps_and_webdav_subitems() {
        let output = format_config_info();

        assert!(output.contains("| FTPS | --- |"));
        assert!(output.contains("| FTPS > Enabled (`RUSTFS_FTPS_ENABLE`) |"));
        assert!(output.contains("| FTPS > Address (`RUSTFS_FTPS_ADDRESS`) |"));
        assert!(output.contains("| FTPS > Passive Ports (`RUSTFS_FTPS_PASSIVE_PORTS`) |"));
        assert!(output.contains("| WebDAV | --- |"));
        assert!(output.contains("| WebDAV > Enabled (`RUSTFS_WEBDAV_ENABLE`) |"));
        assert!(output.contains("| WebDAV > Address (`RUSTFS_WEBDAV_ADDRESS`) |"));
        assert!(output.contains("| WebDAV > Max Body Size (`RUSTFS_WEBDAV_MAX_BODY_SIZE`) |"));
    }
}
