use serde::Deserialize;

#[derive(Deserialize)]
struct SmartJson {
    smart_status: SmartStatus,
    temperature: Option<SmartTemp>,
    ata_smart_attributes: Option<SmartAttrs>,
    nvme_smart_health_information_log: Option<NvmeHealth>,
}

#[derive(Deserialize)]
struct SmartStatus { passed: bool }

#[derive(Deserialize)]
struct SmartTemp {
    #[serde(default)]
    current: Option<i64>,
}

#[derive(Deserialize)]
struct SmartAttrs { table: Vec<SmartAttr> }

#[derive(Deserialize)]
struct SmartAttr {
    id: u8,
    raw: SmartAttrRaw,
    value: Option<i64>,
}

#[derive(Deserialize)]
struct SmartAttrRaw { value: i64 }

#[derive(Deserialize)]
struct NvmeHealth {
    percentage_used: Option<i64>,
    temperature: Option<i64>,
}

pub fn is_available() -> bool {
    std::process::Command::new("which")
        .arg("smartctl")
        .output()
        .map(|o| o.status.success())
        .unwrap_or(false)
}

pub fn parse_json(json: &str) -> Result<crate::payload::SmartMetrics, serde_json::Error> {
    let s: SmartJson = serde_json::from_str(json)?;

    let temperature = s.temperature.as_ref().and_then(|t| t.current)
        .or_else(|| s.nvme_smart_health_information_log.as_ref()?.temperature);

    let mut reallocated = None;
    let mut power_hours = None;
    let mut wear = None;

    if let Some(attrs) = &s.ata_smart_attributes {
        for attr in &attrs.table {
            match attr.id {
                5   => reallocated = Some(attr.raw.value),
                9   => power_hours = Some(attr.raw.value),
                177 => wear = attr.value,
                _   => {}
            }
        }
    }

    if wear.is_none() {
        if let Some(nvme) = &s.nvme_smart_health_information_log {
            if let Some(pct) = nvme.percentage_used {
                wear = Some(100 - pct);
            }
        }
    }

    Ok(crate::payload::SmartMetrics {
        device: String::new(),
        passed: s.smart_status.passed,
        temperature,
        reallocated_sectors: reallocated,
        power_on_hours: power_hours,
        wear_level: wear,
    })
}

pub fn collect() -> Option<Vec<crate::payload::SmartMetrics>> {
    if !is_available() {
        eprintln!("[smart] smartctl introuvable dans PATH");
        return None;
    }
    let mut devs: Vec<String> = std::fs::read_dir("/sys/block")
        .into_iter()
        .flatten()
        .flatten()
        .map(|e| e.file_name().into_string().unwrap_or_default())
        .filter_map(|n| {
            if n.starts_with("sd") {
                Some(format!("/dev/{}", n))
            } else if n.starts_with("nvme") && n.contains('n') {
                Some(format!("/dev/{}", n))
            } else {
                None
            }
        })
        .collect::<std::collections::HashSet<_>>()
        .into_iter()
        .collect();
    devs.sort();
    eprintln!("[smart] disques détectés: {:?}", devs);

    let mut results = Vec::new();
    for dev in &devs {
        let output = match std::process::Command::new("smartctl")
            .args(["-a", "-j", dev])
            .output()
        {
            Ok(o) => o,
            Err(e) => { eprintln!("[smart] erreur exec smartctl {}: {}", dev, e); continue }
        };
        eprintln!("[smart] smartctl {} → exit={}, stdout={} octets, stderr={} octets",
            dev, output.status, output.stdout.len(), output.stderr.len());
        let json = String::from_utf8_lossy(&output.stdout);
        match parse_json(&json) {
            Ok(metrics) => {
                eprintln!("[smart] {} parsé OK (passed={})", dev, metrics.passed);
                results.push(crate::payload::SmartMetrics {
                    device: dev.trim_start_matches("/dev/").to_string(),
                    ..metrics
                });
            }
            Err(e) => {
                eprintln!("[smart] {} parse JSON échoué: {}", dev, e);
                eprintln!("[smart] premiers 200 octets stdout: {:?}", &json.chars().take(200).collect::<String>());
            }
        }
    }
    if results.is_empty() { None } else { Some(results) }
}
