#![cfg_attr(
    all(not(debug_assertions), target_os = "windows"),
    windows_subsystem = "windows"
)]

use serde::{Deserialize, Serialize};
use std::process::{Child, Command, Stdio};
use std::sync::Mutex;
use tauri::Manager;

#[cfg(target_os = "windows")]
use std::os::windows::process::CommandExt;

/// State holding the RustDesk child process
struct RustDeskState {
    process: Mutex<Option<Child>>,
}

#[derive(Serialize, Deserialize, Clone)]
struct RustDeskStatus {
    running: bool,
    pid: Option<u32>,
}

/// Get the path to the RustDesk sidecar binary
fn get_rustdesk_path() -> Result<std::path::PathBuf, String> {
    let exe_dir = std::env::current_exe()
        .map_err(|e| format!("Failed to get current exe path: {}", e))?;

    // In development, look alongside the Tauri binary
    // In production, look in the same directory (Tauri bundles sidecars there)
    let exe_dir = exe_dir
        .parent()
        .ok_or("Failed to get parent directory of exe")?;

    // Try multiple possible locations
    let candidates = vec![
        exe_dir.join("rustdesk.exe"),          // Bundled sidecar (Windows)
        exe_dir.join("binaries").join("rustdesk.exe"), // Explicit binaries dir
        exe_dir.join("rustdesk"),               // Bundled sidecar (Linux/macOS)
        exe_dir.join("binaries").join("rustdesk"), // Explicit binaries dir (Linux/macOS)
    ];

    for candidate in &candidates {
        if candidate.exists() {
            return Ok(candidate.clone());
        }
    }

    Err(format!(
        "RustDesk binary not found. Tried: {:?}",
        candidates
    ))
}

/// Start the RustDesk sidecar process
#[tauri::command]
async fn start_rustdesk(
    state: tauri::State<'_, RustDeskState>,
    app: tauri::AppHandle,
) -> Result<RustDeskStatus, String> {
    let mut proc = state.process.lock().map_err(|e| e.to_string())?;

    // Already running?
    if let Some(ref child) = *proc {
        if let Ok(Some(_)) = child.try_wait() {
            // Process exited, clean up
            *proc = None;
        } else {
            // Still running
            return Ok(RustDeskStatus {
                running: true,
                pid: Some(child.id()),
            });
        }
    }

    let path = get_rustdesk_path()?;

    #[cfg(target_os = "windows")]
    const CREATE_NO_WINDOW: u32 = 0x08000000;
    #[cfg(target_os = "windows")]
    let child = Command::new(&path)
        .arg("--connect")
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .creation_flags(CREATE_NO_WINDOW)
        .spawn()
        .map_err(|e| format!("Failed to start RustDesk: {}", e))?;

    #[cfg(not(target_os = "windows"))]
    let child = Command::new(&path)
        .arg("--connect")
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .map_err(|e| format!("Failed to start RustDesk: {}", e))?;

    let pid = child.id();
    *proc = Some(child);

    // Emit event to frontend
    app.emit("rustdesk-status", RustDeskStatus {
        running: true,
        pid: Some(pid),
    })
    .map_err(|e| e.to_string())?;

    Ok(RustDeskStatus {
        running: true,
        pid: Some(pid),
    })
}

/// Stop the RustDesk sidecar process
#[tauri::command]
async fn stop_rustdesk(
    state: tauri::State<'_, RustDeskState>,
    app: tauri::AppHandle,
) -> Result<RustDeskStatus, String> {
    let mut proc = state.process.lock().map_err(|e| e.to_string())?;

    if let Some(mut child) = proc.take() {
        child.kill().map_err(|e| format!("Failed to kill RustDesk: {}", e))?;
        let _ = child.wait();
    }

    let status = RustDeskStatus {
        running: false,
        pid: None,
    };

    app.emit("rustdesk-status", status.clone())
        .map_err(|e| e.to_string())?;

    Ok(status)
}

/// Get the current status of the RustDesk sidecar
#[tauri::command]
async fn get_rustdesk_status(
    state: tauri::State<'_, RustDeskState>,
) -> Result<RustDeskStatus, String> {
    let mut proc = state.process.lock().map_err(|e| e.to_string())?;

    if let Some(ref mut child) = *proc {
        match child.try_wait() {
            Ok(Some(_status)) => {
                // Process has exited
                *proc = None;
                Ok(RustDeskStatus {
                    running: false,
                    pid: None,
                })
            }
            Ok(None) => {
                // Still running
                Ok(RustDeskStatus {
                    running: true,
                    pid: Some(child.id()),
                })
            }
            Err(e) => Err(format!("Failed to check process status: {}", e)),
        }
    } else {
        Ok(RustDeskStatus {
            running: false,
            pid: None,
        })
    }
}

/// Connect to a remote peer via RustDesk
#[tauri::command]
async fn connect_to_peer(
    state: tauri::State<'_, RustDeskState>,
    app: tauri::AppHandle,
    peer_id: String,
) -> Result<RustDeskStatus, String> {
    // Stop any existing instance
    {
        let mut proc = state.process.lock().map_err(|e| e.to_string())?;
        if let Some(mut child) = proc.take() {
            let _ = child.kill();
            let _ = child.wait();
        }
    }

    let path = get_rustdesk_path()?;

    #[cfg(target_os = "windows")]
    const CREATE_NO_WINDOW: u32 = 0x08000000;
    #[cfg(target_os = "windows")]
    let child = Command::new(&path)
        .arg("--connect")
        .arg(&peer_id)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .creation_flags(CREATE_NO_WINDOW)
        .spawn()
        .map_err(|e| format!("Failed to start RustDesk connection: {}", e))?;

    #[cfg(not(target_os = "windows"))]
    let child = Command::new(&path)
        .arg("--connect")
        .arg(&peer_id)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .map_err(|e| format!("Failed to start RustDesk connection: {}", e))?;

    let pid = child.id();
    {
        let mut proc = state.process.lock().map_err(|e| e.to_string())?;
        *proc = Some(child);
    }

    let status = RustDeskStatus {
        running: true,
        pid: Some(pid),
    };

    app.emit("rustdesk-connected", serde_json::json!({
        "peer_id": peer_id,
        "pid": pid,
    }))
    .map_err(|e| e.to_string())?;

    Ok(status)
}

fn main() {
    tauri::Builder::default()
        .manage(RustDeskState {
            process: Mutex::new(None),
        })
        .invoke_handler(tauri::generate_handler![
            start_rustdesk,
            stop_rustdesk,
            get_rustdesk_status,
            connect_to_peer,
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
