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

pub const MODULE_TULEAP_AUTO_RESOLVE: &str = "tuleap_polling_auto_resolve";
pub const MODULE_GRAYLOG_AUTO_RESOLVE: &str = "graylog_polling_auto_resolve";
pub const MODULE_AI_LIVE_CHAT: &str = "ai_live_chat";
pub const MODULE_AGENT_TASK_RUNNER: &str = "agent_task_runner";

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectModule {
    pub id: String,
    pub project_id: String,
    pub module_key: String,
    pub name: String,
    pub description: String,
    pub enabled: bool,
    pub config_json: String,
    pub created_at: String,
    pub updated_at: String,
}

fn from_row(row: &rusqlite::Row) -> rusqlite::Result<ProjectModule> {
    let enabled_int: i32 = row.get(5)?;

    Ok(ProjectModule {
        id: row.get(0)?,
        project_id: row.get(1)?,
        module_key: row.get(2)?,
        name: row.get(3)?,
        description: row.get(4)?,
        enabled: enabled_int != 0,
        config_json: row.get(6)?,
        created_at: row.get(7)?,
        updated_at: row.get(8)?,
    })
}

fn insert_default(
    conn: &Connection,
    project_id: &str,
    module_key: &str,
    name: &str,
    description: &str,
) -> Result<()> {
    let now = chrono::Utc::now().to_rfc3339();
    conn.execute(
        "INSERT OR IGNORE INTO project_modules \
         (id, project_id, module_key, name, description, enabled, config_json, created_at, updated_at) \
         VALUES (?1, ?2, ?3, ?4, ?5, 1, '{}', ?6, ?7)",
        params![
            Uuid::new_v4().to_string(),
            project_id,
            module_key,
            name,
            description,
            now,
            now
        ],
    )?;

    Ok(())
}

impl ProjectModule {
    pub fn ensure_defaults_for_project(conn: &Connection, project_id: &str) -> Result<()> {
        insert_default(
            conn,
            project_id,
            MODULE_TULEAP_AUTO_RESOLVE,
            "Polling Tuleap + auto-resolve",
            "Surveille Tuleap et lance le pipeline analyste/developpeur.",
        )?;
        insert_default(
            conn,
            project_id,
            MODULE_GRAYLOG_AUTO_RESOLVE,
            "Polling Graylog + auto-resolve",
            "Surveille Graylog, score les sujets, et déclenche le pipeline analyste/developpeur.",
        )?;
        insert_default(
            conn,
            project_id,
            MODULE_AI_LIVE_CHAT,
            "Live chat agent",
            "Discussion live avec un agent sur le contexte du projet.",
        )?;
        insert_default(
            conn,
            project_id,
            MODULE_AGENT_TASK_RUNNER,
            "Agent task runner",
            "File de tâches asynchrones traitées par des agents pré-définis.",
        )?;

        Ok(())
    }

    pub fn list_by_project(conn: &Connection, project_id: &str) -> Result<Vec<ProjectModule>> {
        Self::ensure_defaults_for_project(conn, project_id)?;

        let mut stmt = conn.prepare(
            "SELECT id, project_id, module_key, name, description, enabled, config_json, created_at, updated_at \
             FROM project_modules \
             WHERE project_id = ?1 \
             ORDER BY name ASC",
        )?;
        let rows = stmt.query_map(params![project_id], from_row)?;
        rows.collect()
    }

    pub fn set_enabled(
        conn: &Connection,
        project_id: &str,
        module_key: &str,
        enabled: bool,
    ) -> Result<()> {
        let now = chrono::Utc::now().to_rfc3339();
        let enabled_int = if enabled { 1i32 } else { 0i32 };

        let affected = conn.execute(
            "UPDATE project_modules
             SET enabled = ?1,
                 updated_at = ?2
             WHERE project_id = ?3
               AND module_key = ?4",
            params![enabled_int, now, project_id, module_key],
        )?;

        if affected == 0 {
            return Err(rusqlite::Error::QueryReturnedNoRows);
        }

        Ok(())
    }

    pub fn is_enabled(conn: &Connection, project_id: &str, module_key: &str) -> Result<bool> {
        Self::ensure_defaults_for_project(conn, project_id)?;

        let enabled_int: i32 = conn.query_row(
            "SELECT enabled
             FROM project_modules
             WHERE project_id = ?1 AND module_key = ?2",
            params![project_id, module_key],
            |row| row.get(0),
        )?;

        Ok(enabled_int != 0)
    }
}

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

    #[test]
    fn test_ensure_defaults_for_project_includes_graylog_module() {
        let conn = db::init_in_memory().expect("db init should succeed");
        let project = Project::insert(&conn, "Modules", "/tmp/modules", None, "main")
            .expect("project insert should succeed");

        ProjectModule::ensure_defaults_for_project(&conn, &project.id)
            .expect("ensure defaults should succeed");

        let modules =
            ProjectModule::list_by_project(&conn, &project.id).expect("list should succeed");
        assert_eq!(modules.len(), 4);

        let graylog = modules
            .iter()
            .find(|module| module.module_key == MODULE_GRAYLOG_AUTO_RESOLVE)
            .expect("graylog module should be present");

        assert_eq!(graylog.name, "Polling Graylog + auto-resolve");
        assert_eq!(
            graylog.description,
            "Surveille Graylog, score les sujets, et déclenche le pipeline analyste/developpeur."
        );
        assert!(graylog.enabled);
    }
}
