use std::{mem, ptr};
use windows_sys::Win32::Devices::DeviceAndDriverInstallation::{
    SetupDiGetClassDevsExW, SetupDiGetDevicePropertyW,
};
use windows_sys::Win32::Devices::Properties::DEVPROP_TYPE_BINARY;
use windows_sys::Win32::Foundation::{
    CloseHandle, GetLastError, ERROR_INVALID_DATA, FILETIME, INVALID_HANDLE_VALUE,
};
use windows_sys::Win32::System::Threading::{
    GetProcessTimes, OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION,
};

use crate::windows::{
    device::GUID_NETWORK_ADAPTER,
    ffi::{destroy_device_info_list, encode_utf16, enum_device_info},
    tun::adapter::{get_device_name, DEVPKEY_Wintun_OwningProcess},
};

#[repr(C)]
pub struct OwningProcess {
    process_id: u32,
    creation_time: FILETIME,
}

pub fn check_adapter_if_orphaned_devices_win7(adapter_name: &str) -> bool {
    let device_name = encode_utf16("ROOT\\Wintun");
    let dev_info = unsafe {
        SetupDiGetClassDevsExW(
            &GUID_NETWORK_ADAPTER,
            device_name.as_ptr(),
            ptr::null_mut(),
            0,
            0,
            ptr::null_mut(),
            ptr::null_mut(),
        )
    };
    if dev_info == INVALID_HANDLE_VALUE as isize {
        if unsafe { GetLastError() } != ERROR_INVALID_DATA {
            log::error!("Failed to get adapters");
        }
        return false;
    }

    let mut index = 0;
    let is_orphaned_adapter = loop {
        match enum_device_info(dev_info, index) {
            Some(ret) => {
                let Ok(devinfo_data) = ret else {
                    continue;
                };

                unsafe {
                    let mut ptype = mem::zeroed();
                    let mut buf: [u8; mem::size_of::<OwningProcess>()] = mem::zeroed();

                    let ok = SetupDiGetDevicePropertyW(
                        dev_info,
                        &devinfo_data,
                        &DEVPKEY_Wintun_OwningProcess,
                        &mut ptype,
                        &mut buf as _,
                        buf.len() as _,
                        ptr::null_mut(),
                        0,
                    );

                    if ok != 0 && ptype == DEVPROP_TYPE_BINARY && {
                        let owning_process = buf.as_ptr() as *const OwningProcess;
                        !process_is_stale(&*owning_process)
                    } {
                        continue;
                    }
                }

                let Ok(name) = get_device_name(dev_info, &devinfo_data) else {
                    index += 1;
                    continue;
                };
                if adapter_name == name {
                    break true;
                }
            }
            None => break false,
        }

        index += 1;
    };
    _ = destroy_device_info_list(dev_info);
    is_orphaned_adapter
}

fn process_is_stale(owning_process: &OwningProcess) -> bool {
    let process = unsafe {
        OpenProcess(
            PROCESS_QUERY_LIMITED_INFORMATION,
            0,
            owning_process.process_id,
        )
    };
    if process.is_null() {
        return true;
    }
    let mut creation_time: FILETIME = unsafe { std::mem::zeroed() };
    let mut unused: FILETIME = unsafe { std::mem::zeroed() };
    let ret = unsafe {
        GetProcessTimes(
            process,
            &mut creation_time,
            &mut unused,
            &mut unused,
            &mut unused,
        )
    };
    _ = unsafe { CloseHandle(process) };
    if ret == 0 {
        return false;
    }
    creation_time.dwHighDateTime == owning_process.creation_time.dwHighDateTime
        && creation_time.dwLowDateTime == owning_process.creation_time.dwLowDateTime
}
