use crate::models::notification::Notification;
use rusqlite::Connection;
use std::sync::{Arc, Mutex};
use tauri::{AppHandle, Emitter};

pub fn create(
    db: &Arc<Mutex<Connection>>,
    project_id: &str,
    ticket_id: Option<&str>,
    notification_type: &str,
    title: &str,
    message: &str,
) -> Result<Notification, String> {
    let conn = db.lock().map_err(|e| format!("DB lock failed: {}", e))?;
    Notification::insert(
        &conn,
        project_id,
        ticket_id,
        notification_type,
        title,
        message,
    )
    .map_err(|e| format!("Failed to insert notification: {}", e))
}

pub fn create_and_emit(
    db: &Arc<Mutex<Connection>>,
    app_handle: &AppHandle,
    project_id: &str,
    ticket_id: Option<&str>,
    notification_type: &str,
    title: &str,
    message: &str,
) -> Result<Notification, String> {
    let notification = create(db, project_id, ticket_id, notification_type, title, message)?;

    let _ = app_handle.emit(
        "new-notification",
        serde_json::json!({
            "notification": notification,
        }),
    );

    Ok(notification)
}

fn error_message(error: &str) -> String {
    if error.is_empty() {
        "Erreur lors du traitement du ticket".to_string()
    } else {
        error.to_string()
    }
}

pub fn notify_new_ticket(
    db: &Arc<Mutex<Connection>>,
    app_handle: &AppHandle,
    project_id: &str,
    ticket_id: &str,
    artifact_id: i32,
    artifact_title: &str,
) {
    let title = format!("Nouveau ticket #{}", artifact_id);
    let message = artifact_title.to_string();
    let _ = create_and_emit(
        db,
        app_handle,
        project_id,
        Some(ticket_id),
        "NewTicket",
        &title,
        &message,
    );
}

pub fn notify_analysis_done(
    db: &Arc<Mutex<Connection>>,
    app_handle: &AppHandle,
    project_id: &str,
    ticket_id: &str,
    artifact_id: i32,
) {
    let title = format!("Analyse terminee #{}", artifact_id);
    let message = "Aucune correction de code necessaire".to_string();
    let _ = create_and_emit(
        db,
        app_handle,
        project_id,
        Some(ticket_id),
        "AnalysisDone",
        &title,
        &message,
    );
}

pub fn notify_fix_ready(
    db: &Arc<Mutex<Connection>>,
    app_handle: &AppHandle,
    project_id: &str,
    ticket_id: &str,
    artifact_id: i32,
) {
    let title = format!("Fix pret #{}", artifact_id);
    let message = "Un correctif est disponible pour revue".to_string();
    let _ = create_and_emit(
        db,
        app_handle,
        project_id,
        Some(ticket_id),
        "FixReady",
        &title,
        &message,
    );
}

pub fn notify_error(
    db: &Arc<Mutex<Connection>>,
    app_handle: &AppHandle,
    project_id: &str,
    ticket_id: &str,
    artifact_id: i32,
    error: &str,
) {
    let title = format!("Erreur ticket #{}", artifact_id);
    let message = error_message(error);

    let _ = create_and_emit(
        db,
        app_handle,
        project_id,
        Some(ticket_id),
        "Error",
        &title,
        &message,
    );
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::db;
    use crate::models::agent::{Agent, AgentRole, AgentTool};
    use crate::models::project::Project;
    use crate::models::ticket::ProcessedTicket;
    use crate::models::tracker::WatchedTracker;

    fn setup() -> (Arc<Mutex<Connection>>, String) {
        let conn = db::init_in_memory().expect("db init should succeed");
        let project = Project::insert(&conn, "Notif Project", "/tmp/notif", None, "main")
            .expect("project insert should succeed");
        (Arc::new(Mutex::new(conn)), project.id)
    }

    fn setup_ticket(db: &Arc<Mutex<Connection>>, project_id: &str) -> String {
        let conn = db.lock().expect("db lock should succeed");
        let analyst = Agent::insert(&conn, "A", AgentRole::Analyst, AgentTool::Codex, "").unwrap();
        let developer =
            Agent::insert(&conn, "D", AgentRole::Developer, AgentTool::ClaudeCode, "").unwrap();

        let tracker = WatchedTracker::insert(
            &conn,
            project_id,
            101,
            "Bugs",
            10,
            &analyst.id,
            &developer.id,
            vec![],
        )
        .expect("tracker insert should succeed");

        let ticket = ProcessedTicket::insert_if_new(
            &conn,
            &tracker.id,
            1,
            "Ticket 1",
            "{\"id\":1}",
        )
        .expect("ticket insert should succeed")
        .expect("ticket should be inserted");

        ticket.id
    }

    #[test]
    fn test_create_notification() {
        let (db, project_id) = setup();

        let n = create(
            &db,
            &project_id,
            None,
            "NewTicket",
            "Ticket #12",
            "Nouveau ticket detecte",
        )
        .expect("notification should be inserted");

        assert_eq!(n.project_id, project_id);
        assert_eq!(n.ticket_id, None);
        assert_eq!(n.notification_type, "NewTicket");
        assert_eq!(n.title, "Ticket #12");
        assert_eq!(n.message, "Nouveau ticket detecte");
        assert!(!n.read);
    }

    #[test]
    fn test_create_notification_with_ticket() {
        let (db, project_id) = setup();
        let ticket_id = setup_ticket(&db, &project_id);

        let n = create(
            &db,
            &project_id,
            Some(&ticket_id),
            "FixReady",
            "Fix pret #1",
            "Un correctif est disponible pour revue",
        )
        .expect("notification should be inserted");

        assert_eq!(n.ticket_id.as_deref(), Some(ticket_id.as_str()));
        assert_eq!(n.notification_type, "FixReady");
    }

    #[test]
    fn test_error_message_default_when_empty() {
        assert_eq!(error_message(""), "Erreur lors du traitement du ticket");
    }

    #[test]
    fn test_error_message_keeps_original() {
        assert_eq!(error_message("timeout"), "timeout");
    }
}
