use std::fs;
use std::path::Path;
use std::sync::OnceLock;

use sha2::{Digest, Sha256};
use sysinfo::System;

use crate::default_config::default_device_id_path;

static CACHED: OnceLock<String> = OnceLock::new();

pub fn get_device_fingerprint() -> String {
    CACHED
        .get_or_init(|| load_or_create(&default_device_id_path()))
        .clone()
}

pub fn get_device_name() -> String {
    hostname::get()
        .map(|h| h.to_string_lossy().into_owned())
        .unwrap_or_else(|_| "unknown".into())
}

fn collect_fingerprint_material() -> String {
    let mut parts = Vec::new();

    // CPU
    let mut sys = System::new();
    sys.refresh_cpu();
    let cpu = sys.cpus().first();
    if let Some(cpu) = cpu {
        parts.push(format!(
            "cpu:{}|{}|{}",
            cpu.vendor_id(),
            cpu.brand(),
            sys.cpus().len()
        ));
    } else {
        parts.push("cpu:unknown".into());
    }

    // System
    parts.push(format!(
        "system:{}|{}",
        System::name().unwrap_or_else(|| "unknown".into()),
        System::kernel_version().unwrap_or_else(|| "unknown".into())
    ));

    // Network MAC collection can vary across sysinfo versions and platforms.
    // Keep a stable fallback to avoid fingerprint collection failing.
    parts.push("mac:unknown".into());

    parts.join("|")
}

fn hash_fingerprint_material(material: &str) -> String {
    let mut hasher = Sha256::new();
    hasher.update(material.as_bytes());
    let result = hasher.finalize();
    hex::encode(result)
}

fn load_or_create(path: &Path) -> String {
    if let Ok(existing) = fs::read_to_string(path) {
        let trimmed = existing.trim();
        if !trimmed.is_empty() {
            return trimmed.to_string();
        }
    }
    let material = collect_fingerprint_material();
    let id = hash_fingerprint_material(&material);
    if let Some(parent) = path.parent() {
        let _ = fs::create_dir_all(parent);
    }
    let _ = fs::write(path, &id);
    id
}
