use gtk::gio;
use gtk::prelude::*;

use super::database::Database;
use super::security;

/// Send a desktop notification via gio.
pub fn send_system_notification(
    app: &gio::Application,
    id: &str,
    title: &str,
    body: &str,
    priority: gio::NotificationPriority,
) {
    let notification = gio::Notification::new(title);
    notification.set_body(Some(body));
    notification.set_priority(priority);
    app.send_notification(Some(id), &notification);
}

/// A CVE notification to send to the user.
#[derive(Debug, Clone)]
pub struct CveNotification {
    pub app_name: String,
    pub appimage_id: i64,
    pub severity: String,
    pub cve_count: usize,
    pub affected_libraries: Vec<String>,
}

/// Check for new CVEs and send desktop notifications for any new findings.
/// Returns the list of notifications that were sent.
pub fn check_and_notify(db: &Database, threshold: &str) -> Vec<CveNotification> {
    let records = match db.get_all_appimages() {
        Ok(r) => r,
        Err(e) => {
            log::error!("Failed to get appimages for notification check: {}", e);
            return Vec::new();
        }
    };

    let min_severity = severity_rank(threshold);
    let mut notifications = Vec::new();

    for record in &records {
        let path = std::path::Path::new(&record.path);
        if !path.exists() {
            continue;
        }

        // Get current CVE matches from database
        let cve_matches = db.get_cve_matches(record.id).unwrap_or_default();

        let mut new_cves = Vec::new();
        let mut affected_libs = Vec::new();
        let mut max_severity = String::new();
        let mut max_severity_rank = 0u8;

        for m in &cve_matches {
            let sev = m.severity.as_deref().unwrap_or("MEDIUM");
            let rank = severity_rank(sev);

            // Skip if below threshold
            if rank < min_severity {
                continue;
            }

            // Check if already notified
            if db.has_cve_been_notified(record.id, &m.cve_id).unwrap_or(true) {
                continue;
            }

            new_cves.push(m.cve_id.clone());

            let lib_name = m.library_name.as_deref()
                .unwrap_or(&m.library_soname);
            if !affected_libs.contains(&lib_name.to_string()) {
                affected_libs.push(lib_name.to_string());
            }

            if rank > max_severity_rank {
                max_severity_rank = rank;
                max_severity = sev.to_string();
            }
        }

        if new_cves.is_empty() {
            continue;
        }

        let app_name = record.app_name.as_deref()
            .unwrap_or(&record.filename)
            .to_string();

        let notif = CveNotification {
            app_name: app_name.clone(),
            appimage_id: record.id,
            severity: max_severity,
            cve_count: new_cves.len(),
            affected_libraries: affected_libs,
        };

        // Send desktop notification
        if send_desktop_notification(&notif).is_ok() {
            // Mark all as notified
            for cve_id in &new_cves {
                let sev = cve_matches.iter()
                    .find(|m| m.cve_id == *cve_id)
                    .and_then(|m| m.severity.as_deref())
                    .unwrap_or("MEDIUM");
                db.mark_cve_notified(record.id, cve_id, sev).ok();
            }

            notifications.push(notif);
        }
    }

    notifications
}

/// Send a desktop notification for a CVE finding.
fn send_desktop_notification(notif: &CveNotification) -> Result<(), NotificationError> {
    let summary = format!(
        "Security: {} new CVE{} in {}",
        notif.cve_count,
        if notif.cve_count == 1 { "" } else { "s" },
        notif.app_name,
    );

    let body = format!(
        "Severity: {} - Affected: {}",
        notif.severity,
        notif.affected_libraries.join(", "),
    );

    let urgency = match notif.severity.as_str() {
        "CRITICAL" => notify_rust::Urgency::Critical,
        "HIGH" => notify_rust::Urgency::Normal,
        _ => notify_rust::Urgency::Low,
    };

    notify_rust::Notification::new()
        .appname("Driftwood")
        .summary(&summary)
        .body(&body)
        .icon("security-medium")
        .urgency(urgency)
        .timeout(notify_rust::Timeout::Milliseconds(10000))
        .show()
        .map_err(|e| NotificationError::SendFailed(e.to_string()))?;

    Ok(())
}

/// Run a security scan and send notifications for any new findings.
/// This is the CLI entry point for `driftwood security --notify`.
pub fn scan_and_notify(db: &Database, threshold: &str) -> Vec<CveNotification> {
    // First run a batch scan to get fresh data
    let _results = security::batch_scan(db);

    // Then check for new notifications
    check_and_notify(db, threshold)
}

fn severity_rank(severity: &str) -> u8 {
    match severity.to_uppercase().as_str() {
        "CRITICAL" => 4,
        "HIGH" => 3,
        "MEDIUM" => 2,
        "LOW" => 1,
        _ => 0,
    }
}

#[derive(Debug)]
pub enum NotificationError {
    SendFailed(String),
}

impl std::fmt::Display for NotificationError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::SendFailed(e) => write!(f, "Failed to send notification: {}", e),
        }
    }
}

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

    #[test]
    fn test_severity_rank() {
        assert_eq!(severity_rank("CRITICAL"), 4);
        assert_eq!(severity_rank("HIGH"), 3);
        assert_eq!(severity_rank("MEDIUM"), 2);
        assert_eq!(severity_rank("LOW"), 1);
        assert_eq!(severity_rank("unknown"), 0);
    }

    #[test]
    fn test_severity_rank_case_insensitive() {
        assert_eq!(severity_rank("critical"), 4);
        assert_eq!(severity_rank("High"), 3);
        assert_eq!(severity_rank("medium"), 2);
    }

    #[test]
    fn test_notification_error_display() {
        let err = NotificationError::SendFailed("D-Bus error".to_string());
        assert!(format!("{}", err).contains("D-Bus error"));
    }

    #[test]
    fn test_check_and_notify_empty_db() {
        let db = crate::core::database::Database::open_in_memory().unwrap();
        let notifications = check_and_notify(&db, "high");
        assert!(notifications.is_empty());
    }
}
