use std::path::{Path, PathBuf};

use sysinfo::{System, SystemExt, Pid, PidExt, ProcessExt};
use serde::{Serialize, Deserialize};
use std::os::windows::io::AsRawHandle;

#[cfg(windows)]
use {
    tokio::net::windows::named_pipe::NamedPipeServer,
    windows::Win32::{
        Foundation::HANDLE,
        System::Pipes::GetNamedPipeClientProcessId,
    },
};

#[cfg(unix)]
use tokio::net::UnixStream;

use crate::errors::*;


#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
pub struct Client {
    pub pid: u32,
    pub exe: Option<PathBuf>,
}


#[cfg(unix)]
pub fn get_client_parent(stream: &UnixStream) -> Result<Client, ClientInfoError> {
    let pid = stream.peer_cred()?;
    get_process_parent_info(pid)?
}


#[cfg(windows)]
pub fn get_client_parent(stream: &NamedPipeServer) -> Result<Client, ClientInfoError> {
    let raw_handle = stream.as_raw_handle();
    let mut pid = 0u32;
    let handle = HANDLE(raw_handle as _);
    unsafe { GetNamedPipeClientProcessId(handle, &mut pid as *mut u32)? };

    get_process_parent_info(pid)
}


fn get_process_parent_info(pid: u32) -> Result<Client, ClientInfoError> {
    let sys_pid = Pid::from_u32(pid);
    let mut sys = System::new();   
    sys.refresh_process(sys_pid);
    let proc = sys.process(sys_pid)
        .ok_or(ClientInfoError::ProcessNotFound)?;

    let parent_pid_sys = proc.parent()
        .ok_or(ClientInfoError::ParentPidNotFound)?;
    sys.refresh_process(parent_pid_sys);
    let parent = sys.process(parent_pid_sys)
        .ok_or(ClientInfoError::ParentProcessNotFound)?;

    let exe = match parent.exe() {
        p if p == Path::new("") => None,
        p => Some(PathBuf::from(p)),
    };

    Ok(Client { pid: parent_pid_sys.as_u32(), exe })
}


// async fn get_associated_pids(local_port: u16) -> Result<Vec<u32>, netstat2::error::Error> {
//     let state = APP.get().unwrap().state::<AppState>();
//     let AppConfig {
//         listen_addr: app_listen_addr,
//         listen_port: app_listen_port,
//         ..
//     } = *state.config.read().await;

//     let sockets_iter = netstat2::iterate_sockets_info(
//         AddressFamilyFlags::IPV4,
//         ProtocolFlags::TCP
//     )?;
//     for item in sockets_iter {
//         let sock_info = item?;
//         let proto_info = match sock_info.protocol_socket_info {
//             ProtocolSocketInfo::Tcp(tcp_info) => tcp_info,
//             ProtocolSocketInfo::Udp(_) => {continue;}
//         };

//         if proto_info.local_port == local_port
//             && proto_info.remote_port == app_listen_port
//             && proto_info.local_addr == app_listen_addr
//             && proto_info.remote_addr == app_listen_addr
//         {
//             return Ok(sock_info.associated_pids)
//         }
//     }
//     Ok(vec![])
// }


// Theoretically, on some systems, multiple processes can share a socket
// pub async fn get_clients(local_port: u16) -> Result<Vec<Option<Client>>, ClientInfoError> {
//     let mut clients = Vec::new();    
//     let mut sys = System::new();
//     for p in get_associated_pids(local_port).await? {
//         let pid = Pid::from_u32(p);
//         sys.refresh_process(pid);
//         let proc = sys.process(pid)
//             .ok_or(ClientInfoError::ProcessNotFound)?;

//         let client = Client {
//             pid: p,
//             exe: proc.exe().to_path_buf(),
//         };
//         clients.push(Some(client));
//     }

//     if clients.is_empty() {
//         clients.push(None); 
//     }

//     Ok(clients)
// }
