use crate::models::*;
use crate::VpnInterface;
use serde::de::DeserializeOwned;
use tauri::{
    plugin::{PluginApi, PluginHandle},
    AppHandle, Runtime,
};

#[cfg(target_os = "android")]
const PLUGIN_IDENTIFIER: &str = "app.netrunner.vpn";

#[cfg(target_os = "ios")]
tauri::ios_plugin_binding!(init_plugin_vpn);

pub fn init<R: Runtime, C: DeserializeOwned>(
    _app: &AppHandle<R>,
    api: PluginApi<R, C>,
) -> crate::Result<Vpn<R>> {
    #[cfg(target_os = "android")]
    let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "VpnPlugin")?;
    #[cfg(target_os = "ios")]
    let handle = api.register_ios_plugin(init_plugin_vpn)?;
    Ok(Vpn(handle))
}

pub struct Vpn<R: Runtime>(PluginHandle<R>);

impl<R: Runtime> VpnInterface<R> for Vpn<R> {
    fn start_vpn(&self, config: TunnelConfig, ip: String, port: i32, sni: String) -> crate::Result<()> {
        self.send("startVpn", StartVpnRequest { config, ip, port, sni })
    }

    fn stop_vpn(&self) -> crate::Result<()> {
        self.send("stopVpn", StopVpnRequest {})
    }

    fn check_actual_vpn_status(&self) -> crate::Result<StatusResponse> {
        self.0
            .run_mobile_plugin::<StatusResponse>("checkActualVpnStatus", ())
            .map_err(crate::Error::from)
    }

    fn get_installed_apps(&self) -> crate::Result<Vec<AppInfo>> {
        #[derive(serde::Deserialize)]
        struct Wrapper {
            apps: Vec<AppInfo>,
        }
        let wrapper: Wrapper = self
            .0
            .run_mobile_plugin("getInstalledApps", ())
            .map_err(crate::error::Error::from)?;
        Ok(wrapper.apps)
    }

    fn launch_app(&self, package_name: String) -> crate::Result<()> {
        #[derive(serde::Serialize)]
        struct Payload {
            #[serde(rename = "packageName")]
            package_name: String,
        }
        self.send("launchApp", Payload { package_name })
    }
}

impl<R: Runtime> Vpn<R> {
    fn send<S: serde::Serialize>(&self, cmd: &str, payload: S) -> crate::Result<()> {
        self.0.run_mobile_plugin(cmd, payload).map_err(Into::into)
    }
}
