use sha2::{Digest, Sha256};

use crate::base64::base64url_encode;

#[cfg(target_os = "windows")]
use winreg::RegKey;
#[cfg(target_os = "windows")]
use winreg::enums::{HKEY_LOCAL_MACHINE, KEY_READ, KEY_WOW64_64KEY};

pub(super) fn device_id_hash() -> Option<String> {
    let id = device_id_bytes()?;
    let mut hasher = Sha256::new();
    hasher.update(&id);
    let digest = hasher.finalize();
    Some(base64url_encode(&digest))
}

#[cfg(target_os = "windows")]
fn device_id_bytes() -> Option<Vec<u8>> {
    let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
    let key = hklm
        .open_subkey_with_flags(
            "Software\\Microsoft\\Cryptography",
            KEY_READ | KEY_WOW64_64KEY,
        )
        .ok()?;
    let s: String = key.get_value("MachineGuid").ok()?;
    if s.is_empty() {
        None
    } else {
        Some(s.into_bytes())
    }
}

#[cfg(target_os = "macos")]
fn device_id_bytes() -> Option<Vec<u8>> {
    #[repr(C)]
    struct Timespec {
        tv_sec: i64,
        tv_nsec: i64,
    }

    unsafe extern "C" {
        fn gethostuuid(uuid: *mut u8, timeout: *const Timespec) -> i32;
    }

    let mut uuid = [0u8; 16];
    let ts = Timespec {
        tv_sec: 1,
        tv_nsec: 0,
    };
    let rc = unsafe { gethostuuid(uuid.as_mut_ptr(), &ts) };
    if rc != 0 {
        return None;
    }
    let s = format_uuid(&uuid);
    if s.is_empty() {
        None
    } else {
        Some(s.into_bytes())
    }
}

#[cfg(target_os = "macos")]
fn format_uuid(uuid: &[u8; 16]) -> String {
    format!(
        "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
        uuid[0],
        uuid[1],
        uuid[2],
        uuid[3],
        uuid[4],
        uuid[5],
        uuid[6],
        uuid[7],
        uuid[8],
        uuid[9],
        uuid[10],
        uuid[11],
        uuid[12],
        uuid[13],
        uuid[14],
        uuid[15]
    )
}

#[cfg(all(
    unix,
    not(target_os = "macos"),
    not(target_os = "ios"),
    not(target_os = "android")
))]
fn device_id_bytes() -> Option<Vec<u8>> {
    let mut data = std::fs::read("/etc/machine-id")
        .or_else(|_| std::fs::read("/var/lib/dbus/machine-id"))
        .ok()?;
    if data.last() == Some(&b'\n') {
        data.pop();
    }
    if data.is_empty() { None } else { Some(data) }
}

#[cfg(any(target_os = "ios", target_os = "android"))]
fn device_id_bytes() -> Option<Vec<u8>> {
    None
}
