use std::{io, mem, ptr};

use crate::windows::{
    device::GUID_NETWORK_ADAPTER,
    ffi::{decode_utf16, destroy_device_info_list, encode_utf16, enum_device_info},
};
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
use windows_sys::Win32::Foundation::{DEVPROPKEY, ERROR_INSUFFICIENT_BUFFER};
use windows_sys::{
    core::GUID,
    Win32::{
        Devices::{
            DeviceAndDriverInstallation::{
                CM_Get_DevNode_Status, SetupDiGetClassDevsExW, SetupDiGetDevicePropertyW,
                CM_DEVNODE_STATUS_FLAGS, CR_SUCCESS, DN_HAS_PROBLEM, HDEVINFO, SP_DEVINFO_DATA,
            },
            Properties::DEVPROPID_FIRST_USABLE,
        },
        System::SystemInformation::{GetVersionExA, OSVERSIONINFOA},
    },
};

#[allow(non_upper_case_globals)]
pub const DEVPKEY_Wintun_Name: DEVPROPKEY = DEVPROPKEY {
    fmtid: GUID {
        data1: 0x3361c968,
        data2: 0x2f2e,
        data3: 0x4660,
        data4: [0xb4, 0x7e, 0x69, 0x9c, 0xdc, 0x4c, 0x32, 0xb9],
    },
    pid: DEVPROPID_FIRST_USABLE + 1,
};

#[allow(non_upper_case_globals)]
pub const DEVPKEY_Wintun_OwningProcess: DEVPROPKEY = DEVPROPKEY {
    fmtid: GUID {
        data1: 0x3361c968,
        data2: 0x2f2e,
        data3: 0x4660,
        data4: [0xb4, 0x7e, 0x69, 0x9c, 0xdc, 0x4c, 0x32, 0xb9],
    },
    pid: DEVPROPID_FIRST_USABLE + 3,
};

pub fn check_adapter_if_orphaned_devices(adapter_name: &str) -> bool {
    if is_windows_seven() {
        return super::adapter_win7::check_adapter_if_orphaned_devices_win7(adapter_name);
    }

    let device_name = encode_utf16("SWD\\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 {
        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;
                };

                let Ok(status) = dev_node_status(&devinfo_data) else {
                    index += 1;
                    continue;
                };
                if status & DN_HAS_PROBLEM == 0 {
                    index += 1;
                    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
}

pub fn get_device_name(devinfo: HDEVINFO, devinfo_data: &SP_DEVINFO_DATA) -> io::Result<String> {
    let mut prop_type: u32 = 0;
    let mut required_size: u32 = 0;
    let ok = unsafe {
        SetupDiGetDevicePropertyW(
            devinfo,
            devinfo_data,
            &DEVPKEY_Wintun_Name,
            &mut prop_type,
            ptr::null_mut(),
            0,
            &mut required_size,
            0,
        )
    };
    if ok == 0 {
        let err = io::Error::last_os_error();
        if err.raw_os_error() != Some(ERROR_INSUFFICIENT_BUFFER as i32) {
            return Err(err);
        }
    }

    let mut buf: Vec<u16> = vec![0; (required_size / 2) as usize];

    let ok = unsafe {
        SetupDiGetDevicePropertyW(
            devinfo,
            devinfo_data,
            &DEVPKEY_Wintun_Name,
            &mut prop_type,
            buf.as_mut_ptr() as *mut u8,
            required_size,
            &mut required_size,
            0,
        )
    };
    if ok == 0 {
        return Err(io::Error::last_os_error());
    }

    Ok(decode_utf16(&buf))
}

fn is_windows_seven() -> bool {
    let mut info = OSVERSIONINFOA {
        dwOSVersionInfoSize: mem::size_of::<OSVERSIONINFOA>() as u32,
        dwMajorVersion: 0,
        dwMinorVersion: 0,
        dwBuildNumber: 0,
        dwPlatformId: 0,
        szCSDVersion: [0; 128],
    };

    unsafe {
        if GetVersionExA(&mut info as *mut _) == 0 {
            return false;
        }
    }

    info.dwMajorVersion == 6 && info.dwMinorVersion == 1
}

fn dev_node_status(devinfo_data: &SP_DEVINFO_DATA) -> io::Result<CM_DEVNODE_STATUS_FLAGS> {
    let mut pulstatus = 0;
    let mut pulproblemnumber = 0;

    let cr = unsafe {
        CM_Get_DevNode_Status(
            &mut pulstatus,
            &mut pulproblemnumber,
            devinfo_data.DevInst,
            0,
        )
    };

    if cr != CR_SUCCESS {
        return Err(io::Error::last_os_error());
    }

    Ok(pulstatus)
}
