use std::fs::OpenOptions;
use std::path::Path;
use std::path::PathBuf;
#[cfg(feature = "pci-hotplug")]
use anyhow::anyhow;
use anyhow::Result as AnyHowResult;
use base::open_file_or_duplicate;
use remain::sorted;
use thiserror::Error;
#[cfg(feature = "gpu")]
pub use crate::gpu::do_gpu_display_add;
#[cfg(feature = "gpu")]
pub use crate::gpu::do_gpu_display_list;
#[cfg(feature = "gpu")]
pub use crate::gpu::do_gpu_display_remove;
#[cfg(feature = "gpu")]
pub use crate::gpu::do_gpu_set_display_mouse_mode;
#[cfg(feature = "gpu")]
pub use crate::gpu::ModifyGpuResult;
pub use crate::sys::handle_request;
pub use crate::sys::handle_request_with_timeout;
use crate::BatControlCommand;
use crate::BatControlResult;
use crate::BatteryType;
#[cfg(feature = "audio")]
use crate::SndControlCommand;
use crate::SwapCommand;
use crate::UsbControlCommand;
use crate::UsbControlResult;
use crate::VmRequest;
use crate::VmResponse;
use crate::USB_CONTROL_MAX_PORTS;
#[sorted]
#[derive(Error, Debug)]
enum ModifyBatError {
    #[error("{0}")]
    BatControlErr(BatControlResult),
}
#[sorted]
#[derive(Error, Debug)]
pub enum ModifyUsbError {
    #[error("failed to open device {0}: {1}")]
    FailedToOpenDevice(PathBuf, base::Error),
    #[error("socket failed")]
    SocketFailed,
    #[error("unexpected response: {0}")]
    UnexpectedResponse(VmResponse),
    #[error("{0}")]
    UsbControl(UsbControlResult),
}
pub type ModifyUsbResult<T> = std::result::Result<T, ModifyUsbError>;
pub type VmsRequestResult = std::result::Result<(), ()>;
pub fn vms_request<T: AsRef<Path> + std::fmt::Debug>(
    request: &VmRequest,
    socket_path: T,
) -> VmsRequestResult {
    match handle_request(request, socket_path)? {
        VmResponse::Ok => Ok(()),
        r => {
            println!("unexpected response: {r}");
            Err(())
        }
    }
}
#[cfg(feature = "pci-hotplug")]
pub fn do_net_add<T: AsRef<Path> + std::fmt::Debug>(
    tap_name: &str,
    socket_path: T,
) -> AnyHowResult<u8> {
    let request =
        VmRequest::HotPlugNetCommand(crate::NetControlCommand::AddTap(tap_name.to_owned()));
    let response = handle_request(&request, socket_path).map_err(|()| anyhow!("socket error: "))?;
    match response {
        VmResponse::PciHotPlugResponse { bus } => Ok(bus),
        e => Err(anyhow!("Unexpected response: {:#}", e)),
    }
}
#[cfg(not(feature = "pci-hotplug"))]
pub fn do_net_add<T: AsRef<Path> + std::fmt::Debug>(
    _tap_name: &str,
    _socket_path: T,
) -> AnyHowResult<u8> {
    anyhow::bail!("Unsupported: pci-hotplug feature disabled");
}
#[cfg(feature = "pci-hotplug")]
pub fn do_net_remove<T: AsRef<Path> + std::fmt::Debug>(
    bus_num: u8,
    socket_path: T,
) -> AnyHowResult<()> {
    let request = VmRequest::HotPlugNetCommand(crate::NetControlCommand::RemoveTap(bus_num));
    let response = handle_request(&request, socket_path).map_err(|()| anyhow!("socket error: "))?;
    match response {
        VmResponse::Ok => Ok(()),
        e => Err(anyhow!("Unexpected response: {:#}", e)),
    }
}
#[cfg(not(feature = "pci-hotplug"))]
pub fn do_net_remove<T: AsRef<Path> + std::fmt::Debug>(
    _bus_num: u8,
    _socket_path: T,
) -> AnyHowResult<()> {
    anyhow::bail!("Unsupported: pci-hotplug feature disabled");
}
pub fn do_usb_attach<T: AsRef<Path> + std::fmt::Debug>(
    socket_path: T,
    dev_path: &Path,
) -> ModifyUsbResult<UsbControlResult> {
    let usb_file = open_file_or_duplicate(dev_path, OpenOptions::new().read(true).write(true))
        .map_err(|e| ModifyUsbError::FailedToOpenDevice(dev_path.into(), e))?;
    let request = VmRequest::UsbCommand(UsbControlCommand::AttachDevice { file: usb_file });
    let response =
        handle_request(&request, socket_path).map_err(|_| ModifyUsbError::SocketFailed)?;
    match response {
        VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
        r => Err(ModifyUsbError::UnexpectedResponse(r)),
    }
}
pub fn do_security_key_attach<T: AsRef<Path> + std::fmt::Debug>(
    socket_path: T,
    dev_path: &Path,
) -> ModifyUsbResult<UsbControlResult> {
    let usb_file = open_file_or_duplicate(dev_path, OpenOptions::new().read(true).write(true))
        .map_err(|e| ModifyUsbError::FailedToOpenDevice(dev_path.into(), e))?;
    let request = VmRequest::UsbCommand(UsbControlCommand::AttachSecurityKey { file: usb_file });
    let response =
        handle_request(&request, socket_path).map_err(|_| ModifyUsbError::SocketFailed)?;
    match response {
        VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
        r => Err(ModifyUsbError::UnexpectedResponse(r)),
    }
}
pub fn do_usb_detach<T: AsRef<Path> + std::fmt::Debug>(
    socket_path: T,
    port: u8,
) -> ModifyUsbResult<UsbControlResult> {
    let request = VmRequest::UsbCommand(UsbControlCommand::DetachDevice { port });
    let response =
        handle_request(&request, socket_path).map_err(|_| ModifyUsbError::SocketFailed)?;
    match response {
        VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
        r => Err(ModifyUsbError::UnexpectedResponse(r)),
    }
}
pub fn do_usb_list<T: AsRef<Path> + std::fmt::Debug>(
    socket_path: T,
) -> ModifyUsbResult<UsbControlResult> {
    let mut ports: [u8; USB_CONTROL_MAX_PORTS] = Default::default();
    for (index, port) in ports.iter_mut().enumerate() {
        *port = index as u8
    }
    let request = VmRequest::UsbCommand(UsbControlCommand::ListDevice { ports });
    let response =
        handle_request(&request, socket_path).map_err(|_| ModifyUsbError::SocketFailed)?;
    match response {
        VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
        r => Err(ModifyUsbError::UnexpectedResponse(r)),
    }
}
pub type DoModifyBatteryResult = std::result::Result<(), ()>;
pub fn do_modify_battery<T: AsRef<Path> + std::fmt::Debug>(
    socket_path: T,
    battery_type: &str,
    property: &str,
    target: &str,
) -> DoModifyBatteryResult {
    let response = match battery_type.parse::<BatteryType>() {
        Ok(type_) => match BatControlCommand::new(property.to_string(), target.to_string()) {
            Ok(cmd) => {
                let request = VmRequest::BatCommand(type_, cmd);
                Ok(handle_request(&request, socket_path)?)
            }
            Err(e) => Err(ModifyBatError::BatControlErr(e)),
        },
        Err(e) => Err(ModifyBatError::BatControlErr(e)),
    };
    match response {
        Ok(response) => {
            println!("{}", response);
            Ok(())
        }
        Err(e) => {
            println!("error {}", e);
            Err(())
        }
    }
}
#[cfg(feature = "audio")]
pub fn do_snd_mute_all<T: AsRef<Path> + std::fmt::Debug>(
    socket_path: T,
    muted: bool,
) -> std::result::Result<(), ()> {
    let request = VmRequest::SndCommand(SndControlCommand::MuteAll(muted));
    let response = handle_request(&request, socket_path)?;
    match response {
        VmResponse::Ok => Ok(()),
        e => {
            println!("Unexpected response: {:#}", e);
            Err(())
        }
    }
}
#[cfg(not(feature = "audio"))]
pub fn do_snd_mute_all<T: AsRef<Path> + std::fmt::Debug>(
    _socket_path: T,
    _muted: bool,
) -> std::result::Result<(), ()> {
    println!("Unsupported: audio feature disabled");
    Err(())
}
pub fn do_swap_status<T: AsRef<Path> + std::fmt::Debug>(socket_path: T) -> VmsRequestResult {
    let response = handle_request(&VmRequest::Swap(SwapCommand::Status), socket_path)?;
    match &response {
        VmResponse::SwapStatus(_) => {
            println!("{}", response);
            Ok(())
        }
        r => {
            println!("unexpected response: {r:?}");
            Err(())
        }
    }
}
pub type HandleRequestResult = std::result::Result<VmResponse, ()>;