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

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FilterGroup {
    pub conditions: Vec<Filter>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Filter {
    pub field: String,
    pub operator: String, // "In", "NotIn", "Equals", "NotEquals"
    pub value: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WatchedTracker {
    pub id: String,
    pub project_id: String,
    pub tracker_id: i32,
    pub tracker_label: String,
    pub polling_interval: i32,
    pub analyst_agent_id: Option<String>,
    pub developer_agent_id: Option<String>,
    pub filters: Vec<FilterGroup>,
    pub enabled: bool,
    pub status: String,
    pub last_polled_at: Option<String>,
    pub created_at: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrackerUpdate {
    pub tracker_id: i32,
    pub tracker_label: String,
    pub polling_interval: i32,
    pub analyst_agent_id: String,
    pub developer_agent_id: String,
    pub filters: Vec<FilterGroup>,
    pub enabled: bool,
}

fn normalize_agent_id(agent_id: &str) -> Option<String> {
    let trimmed = agent_id.trim();
    if trimmed.is_empty() {
        None
    } else {
        Some(trimmed.to_string())
    }
}

fn compute_status(analyst_agent_id: &Option<String>, developer_agent_id: &Option<String>) -> String {
    if analyst_agent_id.is_some() && developer_agent_id.is_some() {
        "valid".to_string()
    } else {
        "invalid".to_string()
    }
}

fn from_row(row: &rusqlite::Row) -> rusqlite::Result<WatchedTracker> {
    let filters_json: String = row.get(7)?;
    let enabled_int: i32 = row.get(8)?;

    let filters: Vec<FilterGroup> = serde_json::from_str(&filters_json)
        .map_err(|e| rusqlite::Error::FromSqlConversionFailure(7, rusqlite::types::Type::Text, Box::new(e)))?;

    Ok(WatchedTracker {
        id: row.get(0)?,
        project_id: row.get(1)?,
        tracker_id: row.get(2)?,
        tracker_label: row.get(3)?,
        polling_interval: row.get(4)?,
        analyst_agent_id: row.get(5)?,
        developer_agent_id: row.get(6)?,
        filters,
        enabled: enabled_int != 0,
        status: row.get(9)?,
        last_polled_at: row.get(10)?,
        created_at: row.get(11)?,
    })
}

impl WatchedTracker {
    pub fn insert(
        conn: &Connection,
        project_id: &str,
        tracker_id: i32,
        tracker_label: &str,
        polling_interval: i32,
        analyst_agent_id: &str,
        developer_agent_id: &str,
        filters: Vec<FilterGroup>,
    ) -> Result<WatchedTracker> {
        let id = Uuid::new_v4().to_string();
        let now = chrono::Utc::now().to_rfc3339();

        let filters_json = serde_json::to_string(&filters)
            .map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?;

        let analyst_agent_id = normalize_agent_id(analyst_agent_id);
        let developer_agent_id = normalize_agent_id(developer_agent_id);
        let status = compute_status(&analyst_agent_id, &developer_agent_id);

        conn.execute(
            "INSERT INTO watched_trackers (id, project_id, tracker_id, tracker_label, polling_interval, agent_config_json, filters_json, analyst_agent_id, developer_agent_id, status, created_at) \
             VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)",
            params![
                id,
                project_id,
                tracker_id,
                tracker_label,
                polling_interval,
                "{}",
                filters_json,
                analyst_agent_id,
                developer_agent_id,
                status,
                now,
            ],
        )?;

        Ok(WatchedTracker {
            id,
            project_id: project_id.to_string(),
            tracker_id,
            tracker_label: tracker_label.to_string(),
            polling_interval,
            analyst_agent_id,
            developer_agent_id,
            filters,
            enabled: true,
            status,
            last_polled_at: None,
            created_at: now,
        })
    }

    pub fn list_by_project(conn: &Connection, project_id: &str) -> Result<Vec<WatchedTracker>> {
        let mut stmt = conn.prepare(
            "SELECT id, project_id, tracker_id, tracker_label, polling_interval, analyst_agent_id, developer_agent_id, filters_json, enabled, status, last_polled_at, created_at \
             FROM watched_trackers WHERE project_id = ?1 ORDER BY created_at DESC",
        )?;
        let rows = stmt.query_map(params![project_id], from_row)?;
        rows.collect()
    }

    pub fn list_all_enabled(conn: &Connection) -> Result<Vec<WatchedTracker>> {
        let mut stmt = conn.prepare(
            "SELECT id, project_id, tracker_id, tracker_label, polling_interval, analyst_agent_id, developer_agent_id, filters_json, enabled, status, last_polled_at, created_at \
             FROM watched_trackers WHERE enabled = 1 AND status = 'valid' AND analyst_agent_id IS NOT NULL AND developer_agent_id IS NOT NULL ORDER BY created_at DESC",
        )?;
        let rows = stmt.query_map([], from_row)?;
        rows.collect()
    }

    pub fn get_by_id(conn: &Connection, id: &str) -> Result<WatchedTracker> {
        conn.query_row(
            "SELECT id, project_id, tracker_id, tracker_label, polling_interval, analyst_agent_id, developer_agent_id, filters_json, enabled, status, last_polled_at, created_at \
             FROM watched_trackers WHERE id = ?1",
            params![id],
            from_row,
        )
    }

    pub fn update(conn: &Connection, id: &str, update: TrackerUpdate) -> Result<()> {
        let filters_json = serde_json::to_string(&update.filters)
            .map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?;
        let enabled_int = if update.enabled { 1i32 } else { 0i32 };

        let analyst_agent_id = normalize_agent_id(&update.analyst_agent_id);
        let developer_agent_id = normalize_agent_id(&update.developer_agent_id);
        let status = compute_status(&analyst_agent_id, &developer_agent_id);

        let affected = conn.execute(
            "UPDATE watched_trackers SET tracker_id = ?1, tracker_label = ?2, polling_interval = ?3, filters_json = ?4, analyst_agent_id = ?5, developer_agent_id = ?6, status = ?7, enabled = ?8 WHERE id = ?9",
            params![
                update.tracker_id,
                update.tracker_label,
                update.polling_interval,
                filters_json,
                analyst_agent_id,
                developer_agent_id,
                status,
                enabled_int,
                id
            ],
        )?;

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

    pub fn update_last_polled(conn: &Connection, id: &str) -> Result<()> {
        let now = chrono::Utc::now().to_rfc3339();
        let affected = conn.execute(
            "UPDATE watched_trackers SET last_polled_at = ?1 WHERE id = ?2",
            params![now, id],
        )?;
        if affected == 0 {
            return Err(rusqlite::Error::QueryReturnedNoRows);
        }
        Ok(())
    }

    pub fn delete(conn: &Connection, id: &str) -> Result<()> {
        let affected = conn.execute("DELETE FROM watched_trackers WHERE id = ?1", params![id])?;
        if affected == 0 {
            return Err(rusqlite::Error::QueryReturnedNoRows);
        }
        Ok(())
    }
}

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

    fn setup() -> Connection {
        let conn = db::init_in_memory().expect("db init should succeed");
        Project::insert(&conn, "Test Project", "/path/test", None, "main").unwrap();
        conn
    }

    fn project_id(conn: &Connection) -> String {
        Project::list(conn).unwrap().into_iter().next().unwrap().id
    }

    fn create_agents(conn: &Connection) -> (String, String) {
        let analyst = Agent::insert(
            conn,
            "Analyst",
            AgentRole::Analyst,
            AgentTool::Codex,
            "",
        )
        .unwrap();

        let developer = Agent::insert(
            conn,
            "Developer",
            AgentRole::Developer,
            AgentTool::ClaudeCode,
            "",
        )
        .unwrap();

        (analyst.id, developer.id)
    }

    fn sample_filters() -> Vec<FilterGroup> {
        vec![FilterGroup {
            conditions: vec![Filter {
                field: "status".to_string(),
                operator: "In".to_string(),
                value: vec!["Open".to_string(), "In Progress".to_string()],
            }],
        }]
    }

    #[test]
    fn test_insert_tracker() {
        let conn = setup();
        let pid = project_id(&conn);
        let (analyst_id, developer_id) = create_agents(&conn);

        let tracker = WatchedTracker::insert(
            &conn,
            &pid,
            42,
            "Bug Tracker",
            15,
            &analyst_id,
            &developer_id,
            sample_filters(),
        )
        .expect("insert should succeed");

        assert!(!tracker.id.is_empty());
        assert_eq!(tracker.project_id, pid);
        assert_eq!(tracker.tracker_id, 42);
        assert_eq!(tracker.tracker_label, "Bug Tracker");
        assert_eq!(tracker.polling_interval, 15);
        assert!(tracker.enabled);
        assert_eq!(tracker.status, "valid");
        assert_eq!(tracker.analyst_agent_id.as_deref(), Some(analyst_id.as_str()));
        assert_eq!(tracker.developer_agent_id.as_deref(), Some(developer_id.as_str()));
        assert!(tracker.last_polled_at.is_none());
        assert!(!tracker.created_at.is_empty());
        assert_eq!(tracker.filters.len(), 1);
    }

    #[test]
    fn test_list_by_project() {
        let conn = setup();
        let pid = project_id(&conn);
        let (analyst_id, developer_id) = create_agents(&conn);

        WatchedTracker::insert(&conn, &pid, 1, "Tracker A", 10, &analyst_id, &developer_id, vec![]).unwrap();
        WatchedTracker::insert(&conn, &pid, 2, "Tracker B", 20, &analyst_id, &developer_id, vec![]).unwrap();

        let trackers = WatchedTracker::list_by_project(&conn, &pid).expect("list should succeed");
        assert_eq!(trackers.len(), 2);
    }

    #[test]
    fn test_list_all_enabled_ignores_invalid() {
        let conn = setup();
        let pid = project_id(&conn);
        let (analyst_id, developer_id) = create_agents(&conn);

        let valid = WatchedTracker::insert(&conn, &pid, 1, "Valid", 10, &analyst_id, &developer_id, vec![]).unwrap();
        WatchedTracker::insert(&conn, &pid, 2, "Invalid", 10, "", &developer_id, vec![]).unwrap();

        let enabled = WatchedTracker::list_all_enabled(&conn).expect("list_all_enabled should succeed");
        assert_eq!(enabled.len(), 1);
        assert_eq!(enabled[0].id, valid.id);
    }

    #[test]
    fn test_get_by_id() {
        let conn = setup();
        let pid = project_id(&conn);
        let (analyst_id, developer_id) = create_agents(&conn);

        let created = WatchedTracker::insert(
            &conn,
            &pid,
            99,
            "My Tracker",
            30,
            &analyst_id,
            &developer_id,
            sample_filters(),
        )
        .unwrap();

        let found = WatchedTracker::get_by_id(&conn, &created.id).expect("get_by_id should succeed");
        assert_eq!(found.id, created.id);
        assert_eq!(found.tracker_id, 99);
        assert_eq!(found.tracker_label, "My Tracker");
        assert_eq!(found.polling_interval, 30);
        assert_eq!(found.filters.len(), 1);
    }

    #[test]
    fn test_update_tracker() {
        let conn = setup();
        let pid = project_id(&conn);
        let (analyst_id, developer_id) = create_agents(&conn);

        let created = WatchedTracker::insert(
            &conn,
            &pid,
            10,
            "Original",
            5,
            &analyst_id,
            &developer_id,
            sample_filters(),
        )
        .unwrap();

        let new_filters = vec![FilterGroup {
            conditions: vec![Filter {
                field: "priority".to_string(),
                operator: "Equals".to_string(),
                value: vec!["High".to_string()],
            }],
        }];

        WatchedTracker::update(
            &conn,
            &created.id,
            TrackerUpdate {
                tracker_id: 11,
                tracker_label: "Updated tracker".to_string(),
                polling_interval: 60,
                analyst_agent_id: analyst_id,
                developer_agent_id: developer_id,
                filters: new_filters,
                enabled: false,
            },
        )
        .expect("update should succeed");

        let updated = WatchedTracker::get_by_id(&conn, &created.id).unwrap();
        assert_eq!(updated.tracker_id, 11);
        assert_eq!(updated.tracker_label, "Updated tracker");
        assert_eq!(updated.polling_interval, 60);
        assert!(!updated.enabled);
        assert_eq!(updated.status, "valid");
        assert_eq!(updated.filters[0].conditions[0].field, "priority");
    }

    #[test]
    fn test_update_last_polled() {
        let conn = setup();
        let pid = project_id(&conn);
        let (analyst_id, developer_id) = create_agents(&conn);

        let created = WatchedTracker::insert(
            &conn,
            &pid,
            5,
            "Poller",
            10,
            &analyst_id,
            &developer_id,
            vec![],
        )
        .unwrap();

        assert!(created.last_polled_at.is_none());

        WatchedTracker::update_last_polled(&conn, &created.id).expect("update_last_polled should succeed");

        let updated = WatchedTracker::get_by_id(&conn, &created.id).unwrap();
        assert!(updated.last_polled_at.is_some());
    }

    #[test]
    fn test_delete_tracker() {
        let conn = setup();
        let pid = project_id(&conn);
        let (analyst_id, developer_id) = create_agents(&conn);

        let created = WatchedTracker::insert(
            &conn,
            &pid,
            7,
            "ToDelete",
            10,
            &analyst_id,
            &developer_id,
            vec![],
        )
        .unwrap();

        WatchedTracker::delete(&conn, &created.id).expect("delete should succeed");

        let result = WatchedTracker::get_by_id(&conn, &created.id);
        assert!(result.is_err());
    }
}
