Skip to content
use crate::config::{self, ConfigState};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SendNotificationResult {
    pub success: bool,
    pub message: String,
    pub notification: Option<serde_json::Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectionTestResult {
    pub ok: bool,
    pub message: String,
}

#[tauri::command]
pub fn get_config() -> Result<ConfigState, String> {
    Ok(config::load_config())
}

#[tauri::command]
pub fn set_active_profile(name: String) -> Result<ConfigState, String> {
    config::set_active_profile(&name)
}

#[tauri::command]
pub fn save_server_config(
    name: String,
    url: String,
    pin: String,
    make_active: bool,
) -> Result<ConfigState, String> {
    config::save_profile(&name, &url, &pin, make_active)
}

#[tauri::command]
pub fn test_connection(
    target_url: Option<String>,
    target_pin: Option<String>,
) -> Result<ConnectionTestResult, String> {
    let cfg = config::load_config();
    let url = target_url
        .filter(|s| !s.trim().is_empty())
        .unwrap_or(cfg.active_server.url)
        .trim()
        .trim_end_matches('/')
        .to_string();
    let pin = target_pin
        .filter(|s| !s.trim().is_empty())
        .unwrap_or(cfg.active_server.pin)
        .trim()
        .to_string();

    let up_url = format!("{url}/up");
    let resp = ureq::get(&up_url)
        .timeout(std::time::Duration::from_secs(5))
        .call()
        .map_err(|e| format!("Server unreachable at {url}: {e}"))?;

    if resp.status() != 200 {
        return Ok(ConnectionTestResult {
            ok: false,
            message: format!("Server returned HTTP {}", resp.status()),
        });
    }

    // Also check PIN auth against /notifications/scan
    let scan_url = format!("{url}/notifications/scan");
    let scan_resp = ureq::post(&scan_url)
        .set("X-PIN", &pin)
        .timeout(std::time::Duration::from_secs(5))
        .send_json(serde_json::json!({ "limit": 1 }));

    match scan_resp {
        Ok(r) => {
            if r.status() == 200 || r.status() == 204 {
                Ok(ConnectionTestResult {
                    ok: true,
                    message: "Connected and authenticated successfully".to_string(),
                })
            } else {
                Ok(ConnectionTestResult {
                    ok: false,
                    message: format!("Unexpected status: {}", r.status()),
                })
            }
        }
        Err(ureq::Error::Status(401, _)) => Ok(ConnectionTestResult {
            ok: false,
            message: "Authentication failed: Invalid PIN".to_string(),
        }),
        Err(e) => Ok(ConnectionTestResult {
            ok: false,
            message: format!("Scan check failed: {e}"),
        }),
    }
}

#[tauri::command]
pub fn send_notification(
    kind: Option<String>,
    message: Option<String>,
    target_url: Option<String>,
    target_pin: Option<String>,
) -> Result<SendNotificationResult, String> {
    let cfg = config::load_config();
    let url = target_url
        .filter(|s| !s.trim().is_empty())
        .unwrap_or(cfg.active_server.url)
        .trim()
        .trim_end_matches('/')
        .to_string();
    let pin = target_pin
        .filter(|s| !s.trim().is_empty())
        .unwrap_or(cfg.active_server.pin)
        .trim()
        .to_string();

    let resolved_kind = kind.as_deref().unwrap_or("notification").trim();
    let default_msg = match resolved_kind {
        "heart" => "Look at your phone",
        "cat" => "Missing you",
        "emergency" => "Emergency",
        _ => "Notification",
    };
    let resolved_msg = message
        .as_deref()
        .filter(|s| !s.trim().is_empty())
        .unwrap_or(default_msg)
        .to_string();

    // 1. Try modern JSON endpoint: POST /api/notify
    let api_url = format!("{url}/api/notify");
    let payload = serde_json::json!({
        "kind": resolved_kind,
        "message": resolved_msg
    });

    let json_req = ureq::post(&api_url)
        .set("X-PIN", &pin)
        .timeout(std::time::Duration::from_secs(10))
        .send_json(&payload);

    match json_req {
        Ok(resp) => {
            let notification: Option<serde_json::Value> = resp.into_json().ok();
            Ok(SendNotificationResult {
                success: true,
                message: format!("Notification '{resolved_kind}' sent successfully"),
                notification,
            })
        }
        Err(ureq::Error::Status(404, _)) => {
            // Fallback to legacy path endpoint: POST /notify/{kind}?message=...
            let encoded_msg = urlencoding_simple(&resolved_msg);
            let legacy_url = format!("{url}/notify/{resolved_kind}?message={encoded_msg}");
            let legacy_resp = ureq::post(&legacy_url)
                .set("X-PIN", &pin)
                .timeout(std::time::Duration::from_secs(10))
                .call()
                .map_err(|e| format!("Failed to send notification: {e}"))?;

            if legacy_resp.status() == 200 || legacy_resp.status() == 204 {
                Ok(SendNotificationResult {
                    success: true,
                    message: format!(
                        "Notification '{resolved_kind}' sent successfully (legacy route)"
                    ),
                    notification: None,
                })
            } else {
                Err(format!("Server returned HTTP {}", legacy_resp.status()))
            }
        }
        Err(ureq::Error::Status(401, _)) => {
            Err("Unauthorized: Server rejected the PIN".to_string())
        }
        Err(e) => Err(format!("Network error: {e}")),
    }
}

pub fn urlencoding_simple(s: &str) -> String {
    let mut out = String::new();
    for b in s.bytes() {
        match b {
            b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
                out.push(b as char);
            }
            b' ' => out.push('+'),
            _ => out.push_str(&format!("%{:02X}", b)),
        }
    }
    out
}

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

    #[test]
    fn test_urlencoding_simple() {
        assert_eq!(urlencoding_simple("hello world"), "hello+world");
        assert_eq!(urlencoding_simple("a/b?c"), "a%2Fb%3Fc");
    }
}