use rusqlite::{params, Connection, Result};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Notification {
    pub id: String,
    pub project_id: String,
    pub ticket_id: Option<String>,
    pub notification_type: String,
    pub title: String,
    pub message: String,
    pub read: bool,
    pub created_at: String,
}

fn from_row(row: &rusqlite::Row) -> rusqlite::Result<Notification> {
    let read_int: i32 = row.get(6)?;
    Ok(Notification {
        id: row.get(0)?,
        project_id: row.get(1)?,
        ticket_id: row.get(2)?,
        notification_type: row.get(3)?,
        title: row.get(4)?,
        message: row.get(5)?,
        read: read_int != 0,
        created_at: row.get(7)?,
    })
}

impl Notification {
    pub fn insert(
        conn: &Connection,
        project_id: &str,
        ticket_id: Option<&str>,
        notification_type: &str,
        title: &str,
        message: &str,
    ) -> Result<Notification> {
        let id = Uuid::new_v4().to_string();
        let now = chrono::Utc::now().to_rfc3339();

        conn.execute(
            "INSERT INTO notifications (id, project_id, ticket_id, type, title, message, read, created_at) \
             VALUES (?1, ?2, ?3, ?4, ?5, ?6, 0, ?7)",
            params![id, project_id, ticket_id, notification_type, title, message, now],
        )?;

        Ok(Notification {
            id,
            project_id: project_id.to_string(),
            ticket_id: ticket_id.map(String::from),
            notification_type: notification_type.to_string(),
            title: title.to_string(),
            message: message.to_string(),
            read: false,
            created_at: now,
        })
    }

    pub fn list_by_project(
        conn: &Connection,
        project_id: &str,
        unread_only: bool,
    ) -> Result<Vec<Notification>> {
        let sql = if unread_only {
            "SELECT id, project_id, ticket_id, type, title, message, read, created_at \
             FROM notifications WHERE project_id = ?1 AND read = 0 ORDER BY created_at DESC"
        } else {
            "SELECT id, project_id, ticket_id, type, title, message, read, created_at \
             FROM notifications WHERE project_id = ?1 ORDER BY created_at DESC"
        };

        let mut stmt = conn.prepare(sql)?;
        let rows = stmt.query_map(params![project_id], from_row)?;
        rows.collect()
    }

    pub fn mark_read(conn: &Connection, id: &str) -> Result<()> {
        conn.execute("UPDATE notifications SET read = 1 WHERE id = ?1", params![id])?;
        Ok(())
    }

    pub fn mark_all_read(conn: &Connection, project_id: &str) -> Result<usize> {
        let affected = conn.execute(
            "UPDATE notifications SET read = 1 WHERE project_id = ?1 AND read = 0",
            params![project_id],
        )?;
        Ok(affected)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::db;
    use crate::models::project::Project;

    fn setup() -> (Connection, String) {
        let conn = db::init_in_memory().expect("db init should succeed");
        let project = Project::insert(&conn, "P", "/tmp/p", None, "main").unwrap();
        (conn, project.id)
    }

    #[test]
    fn test_insert_and_list() {
        let (conn, project_id) = setup();

        let n = Notification::insert(
            &conn,
            &project_id,
            None,
            "NewTicket",
            "Ticket #1",
            "Nouveau ticket detecte",
        )
        .unwrap();

        assert_eq!(n.notification_type, "NewTicket");
        assert!(!n.read);

        let list = Notification::list_by_project(&conn, &project_id, false).unwrap();
        assert_eq!(list.len(), 1);
        assert_eq!(list[0].id, n.id);
    }

    #[test]
    fn test_mark_read() {
        let (conn, project_id) = setup();

        let n = Notification::insert(&conn, &project_id, None, "Error", "Oops", "Erreur").unwrap();
        Notification::mark_read(&conn, &n.id).unwrap();

        let unread = Notification::list_by_project(&conn, &project_id, true).unwrap();
        assert_eq!(unread.len(), 0);
    }

    #[test]
    fn test_mark_all_read() {
        let (conn, project_id) = setup();

        Notification::insert(&conn, &project_id, None, "NewTicket", "A", "B").unwrap();
        Notification::insert(&conn, &project_id, None, "FixReady", "C", "D").unwrap();

        let affected = Notification::mark_all_read(&conn, &project_id).unwrap();
        assert_eq!(affected, 2);

        let unread = Notification::list_by_project(&conn, &project_id, true).unwrap();
        assert_eq!(unread.len(), 0);
    }
}
