use crate::core::memory_stats::MemoryStats;
use serde::Serialize;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StructuredFormat {
    Json,
    Yaml,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StructuredView {
    Compact,
    Extended,
}

#[derive(Serialize)]
struct PercentValue {
    percent: f64,
}

#[derive(Serialize)]
struct CompactMemory {
    total_bytes: u64,
    used_bytes: u64,
    used: PercentValue,
    available_bytes: u64,
    available: PercentValue,
}

#[derive(Serialize)]
struct CompactSwap {
    total_bytes: u64,
    used_bytes: u64,
    used: PercentValue,
}

#[derive(Serialize)]
struct CompactOutput {
    memory: CompactMemory,
    swap: CompactSwap,
}

#[derive(Serialize)]
struct ExtendedMemory {
    total_bytes: u64,
    used_bytes: u64,
    used: PercentValue,
    cache_bytes: u64,
    cache: PercentValue,
    available_bytes: u64,
    available: PercentValue,
}

#[derive(Serialize)]
struct ExtendedSwap {
    total_bytes: u64,
    used_bytes: u64,
    used: PercentValue,
    free_bytes: u64,
    free: PercentValue,
}

#[derive(Serialize)]
struct CacheBreakdown {
    cached_bytes: u64,
    sreclaimable_bytes: u64,
    shmem_bytes: u64,
}

#[derive(Serialize)]
struct ExtendedOutput {
    memory: ExtendedMemory,
    swap: ExtendedSwap,
    cache_breakdown: CacheBreakdown,
}

pub fn render(
    s: &MemoryStats,
    format: StructuredFormat,
    view: StructuredView,
) -> Result<String, String> {
    let content = match view {
        StructuredView::Compact => serialize_compact(s, format)?,
        StructuredView::Extended => serialize_extended(s, format)?,
    };
    Ok(content)
}

fn serialize_compact(s: &MemoryStats, format: StructuredFormat) -> Result<String, String> {
    let out = CompactOutput {
        memory: CompactMemory {
            total_bytes: s.mem_total,
            used_bytes: s.mem_used(),
            used: PercentValue {
                percent: s.mem_used_percent(),
            },
            available_bytes: s.mem_available,
            available: PercentValue {
                percent: s.mem_available_percent(),
            },
        },
        swap: CompactSwap {
            total_bytes: s.swap_total,
            used_bytes: s.swap_used(),
            used: PercentValue {
                percent: s.swap_used_percent(),
            },
        },
    };
    serialize(&out, format)
}

fn serialize_extended(s: &MemoryStats, format: StructuredFormat) -> Result<String, String> {
    let swap_free_pct = if s.swap_total == 0 {
        0.0
    } else {
        (s.swap_free as f64) * 100.0 / (s.swap_total as f64)
    };
    let out = ExtendedOutput {
        memory: ExtendedMemory {
            total_bytes: s.mem_total,
            used_bytes: s.mem_used(),
            used: PercentValue {
                percent: s.mem_used_percent(),
            },
            cache_bytes: s.mem_cache_effective(),
            cache: PercentValue {
                percent: s.mem_cache_percent(),
            },
            available_bytes: s.mem_available,
            available: PercentValue {
                percent: s.mem_available_percent(),
            },
        },
        swap: ExtendedSwap {
            total_bytes: s.swap_total,
            used_bytes: s.swap_used(),
            used: PercentValue {
                percent: s.swap_used_percent(),
            },
            free_bytes: s.swap_free,
            free: PercentValue {
                percent: swap_free_pct,
            },
        },
        cache_breakdown: CacheBreakdown {
            cached_bytes: s.mem_cached,
            sreclaimable_bytes: s.mem_sreclaimable,
            shmem_bytes: s.mem_shmem,
        },
    };
    serialize(&out, format)
}

fn serialize<T: Serialize>(value: &T, format: StructuredFormat) -> Result<String, String> {
    match format {
        StructuredFormat::Json => serde_json::to_string_pretty(value)
            .map_err(|e| format!("json serialization failed: {e}")),
        StructuredFormat::Yaml => serde_yaml::to_string(value)
            .map(|s| s.strip_suffix('\n').unwrap_or(&s).to_string())
            .map_err(|e| format!("yaml serialization failed: {e}")),
    }
}
