use serde::{Deserialize, Serialize};
use uuid::Uuid;

/// Information about a single window on the client machine.
/// `label` is a human-readable, lowercase identifier (e.g. "google_chrome", "discord").
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WindowInfo {
    pub id: u64,
    pub title: String,
    pub label: String,
    pub visible: bool,
}

/// Validate a device/window label: lowercase, no whitespace, only a-z 0-9 - _
pub fn is_valid_label(s: &str) -> bool {
    !s.is_empty()
        && s.chars()
            .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-' || c == '_')
}

/// Convert an arbitrary string into a valid label.
/// Lowercase, replace whitespace and invalid chars with '_', collapse runs.
pub fn sanitize_label(s: &str) -> String {
    let mut result = String::with_capacity(s.len());
    let mut prev_underscore = false;
    for c in s.chars() {
        if c.is_ascii_alphanumeric() {
            result.push(c.to_ascii_lowercase());
            prev_underscore = false;
        } else if c == '-' {
            result.push('-');
            prev_underscore = false;
        } else {
            // Replace whitespace and other chars with _
            if !prev_underscore && !result.is_empty() {
                result.push('_');
                prev_underscore = true;
            }
        }
    }
    // Trim trailing _
    result.trim_end_matches('_').to_string()
}

/// Messages sent from the relay server to a connected client
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ServerMessage {
    /// Request a full-screen screenshot
    ScreenshotRequest { request_id: Uuid },
    /// Capture a specific window by its HWND
    WindowScreenshotRequest { request_id: Uuid, window_id: u64 },
    /// Fetch the last N lines of the client log file
    LogsRequest { request_id: Uuid, lines: u32 },
    /// Show a MessageBox on the client asking the user to do something
    PromptRequest {
        request_id: Uuid,
        message: String,
        title: Option<String>,
    },
    /// Show a non-blocking notification to the user (fire-and-forget)
    InformRequest {
        request_id: Uuid,
        message: String,
        title: Option<String>,
    },
    /// Execute a shell command on the client
    ExecRequest {
        request_id: Uuid,
        command: String,
        timeout_ms: Option<u64>,
    },
    /// Acknowledge a client message
    Ack { request_id: Uuid },
    /// Server-side error response
    Error {
        request_id: Option<Uuid>,
        message: String,
    },
    /// List all visible windows on the client
    ListWindowsRequest { request_id: Uuid },
    /// Minimize all windows (like Win+D)
    MinimizeAllRequest { request_id: Uuid },
    /// Bring a window to the foreground
    FocusWindowRequest { request_id: Uuid, window_id: u64 },
    /// Maximize a window and bring it to the foreground
    MaximizeAndFocusRequest { request_id: Uuid, window_id: u64 },
    /// Request client version info
    VersionRequest { request_id: Uuid },
    /// Upload a file to the client
    UploadRequest {
        request_id: Uuid,
        path: String,
        content_base64: String,
    },
    /// Download a file from the client
    DownloadRequest {
        request_id: Uuid,
        path: String,
    },
    /// Launch a program on the client (fire-and-forget)
    RunRequest {
        request_id: Uuid,
        program: String,
        args: Vec<String>,
    },
    /// Get the contents of the client's clipboard
    ClipboardGetRequest { request_id: Uuid },
    /// Set the contents of the client's clipboard
    ClipboardSetRequest { request_id: Uuid, text: String },
}

/// Messages sent from the client to the relay server
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ClientMessage {
    /// Client registers itself with its device label
    Hello { label: String },
    /// Response to a screenshot request — base64-encoded PNG
    ScreenshotResponse {
        request_id: Uuid,
        image_base64: String,
        width: u32,
        height: u32,
    },
    /// Response to an exec request
    ExecResponse {
        request_id: Uuid,
        stdout: String,
        stderr: String,
        exit_code: i32,
    },
    /// Generic acknowledgement
    Ack { request_id: Uuid },
    /// Client error response
    Error {
        request_id: Uuid,
        message: String,
    },
    /// Response to a list-windows request
    ListWindowsResponse {
        request_id: Uuid,
        windows: Vec<WindowInfo>,
    },
    /// Response to a version request
    VersionResponse {
        request_id: Uuid,
        version: String,
        commit: String,
    },
    LogsResponse {
        request_id: Uuid,
        content: String,
        log_path: String,
    },
    /// Response to a download request
    DownloadResponse {
        request_id: Uuid,
        content_base64: String,
        size: u64,
    },
    /// Response to a clipboard-get request
    ClipboardGetResponse { request_id: Uuid, text: String },
    /// Response to a prompt request
    PromptResponse { request_id: Uuid, answer: String },
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_valid_labels() {
        assert!(is_valid_label("moritz_pc"));
        assert!(is_valid_label("my-desktop"));
        assert!(is_valid_label("pc01"));
        assert!(!is_valid_label("Moritz PC"));
        assert!(!is_valid_label(""));
        assert!(!is_valid_label("has spaces"));
        assert!(!is_valid_label("UPPER"));
    }

    #[test]
    fn test_sanitize_label() {
        assert_eq!(sanitize_label("Moritz PC"), "moritz_pc");
        assert_eq!(sanitize_label("My  Desktop!!"), "my_desktop");
        assert_eq!(sanitize_label("hello-world"), "hello-world");
        assert_eq!(sanitize_label("DESKTOP-ABC123"), "desktop-abc123");
    }

    #[test]
    fn test_server_message_serialization() {
        let msg = ServerMessage::ExecRequest {
            request_id: Uuid::nil(),
            command: "echo hello".into(),
            timeout_ms: None,
        };
        let json = serde_json::to_string(&msg).unwrap();
        assert!(json.contains("exec_request"));
        assert!(json.contains("echo hello"));
    }

    #[test]
    fn test_client_message_serialization() {
        let msg = ClientMessage::Hello { label: "test-pc".into() };
        let json = serde_json::to_string(&msg).unwrap();
        assert!(json.contains("hello"));
        assert!(json.contains("test-pc"));
    }
}
