#![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, WebviewUrl, WebviewWindowBuilder};

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

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

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

/// Find the RustDesk 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))?;

    let exe_dir = exe_dir
        .parent()
        .ok_or("Failed to get parent directory of exe")?;

    let candidates = vec![
        exe_dir.join("rustdesk.exe"),
        exe_dir.join("binaries").join("rustdesk.exe"),
        exe_dir.join("rustdesk"),
        exe_dir.join("binaries").join("rustdesk"),
    ];

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

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

/// Start the RustDesk server process in background (--server mode)
/// This enables the machine to receive incoming connections
#[tauri::command]
async fn start_rustdesk_server(
    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() {
            *proc = None;
        } else {
            return Ok(RustDeskStatus {
                running: true,
                pid: Some(child.id()),
            });
        }
    }

    let path = get_rustdesk_path()?;

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

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

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

    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 server process
#[tauri::command]
async fn stop_rustdesk_server(
    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 server status
#[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(_)) => {
                *proc = None;
                Ok(RustDeskStatus {
                    running: false,
                    pid: None,
                })
            }
            Ok(None) => Ok(RustDeskStatus {
                running: true,
                pid: Some(child.id()),
            }),
            Err(e) => Err(format!("Failed to check process: {}", e)),
        }
    } else {
        Ok(RustDeskStatus {
            running: false,
            pid: None,
        })
    }
}

/// Open the RustDesk web client in a new Tauri webview window.
/// The web client is the Flutter web build embedded in the app.
/// It connects to the local RustDesk server via WebSocket.
#[tauri::command]
async fn open_rustdesk_web(
    app: tauri::AppHandle,
    server_url: Option<String>,
) -> Result<(), String> {
    // The RustDesk web client is served from the embedded rustdesk-web/ assets.
    // In dev: looks at ../rustdesk-web/index.html
    // In prod: bundled as Tauri resource
    let web_path = std::env::current_dir()
        .unwrap_or_default()
        .parent()
        .map(|p| p.join("rustdesk-web").join("index.html"))
        .filter(|p| p.exists());

    let url = if let Some(path) = web_path {
        let path_str = path.to_string_lossy().to_string();
        // On Windows, convert to file:// URL
        #[cfg(target_os = "windows")]
        let url = format!("file:///{0}", path_str.replace('\\', "/"));
        #[cfg(not(target_os = "windows"))]
        let url = format!("file://{0}", path_str);

        // Append server URL as query param if provided
        if let Some(ref srv) = server_url {
            format!("{}?server={}", url, srv)
        } else {
            url
        }
    } else {
        // Fallback: try loading from Tauri asset protocol
        // This requires the web client to be bundled via tauri.conf.json resources
        "https://web.rustdesk.com".to_string()
    };

    WebviewWindowBuilder::new(
        &app,
        "rustdesk-web",
        tauri::WebviewUrl::External(url.parse().map_err(|e| format!("Invalid URL: {}", e))?),
    )
    .title("RustDesk - Remote Desktop")
    .fullscreen(true)
    .inner_size(1280.0, 720.0)
    .build()
    .map_err(|e| format!("Failed to create webview: {}", e))?;

    Ok(())
}

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