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

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Project {
    pub id: String,
    pub name: String,
    pub path: String,
    pub cloned_from: Option<String>,
    pub base_branch: String,
    pub created_at: String,
}

impl Project {
    pub fn insert(
        conn: &Connection,
        name: &str,
        path: &str,
        cloned_from: Option<&str>,
        base_branch: &str,
    ) -> Result<Project> {
        let id = Uuid::new_v4().to_string();
        let now = chrono::Utc::now().to_rfc3339();

        conn.execute(
            "INSERT INTO projects (id, name, path, cloned_from, base_branch, created_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
            params![id, name, path, cloned_from, base_branch, now],
        )?;

        Ok(Project {
            id,
            name: name.to_string(),
            path: path.to_string(),
            cloned_from: cloned_from.map(String::from),
            base_branch: base_branch.to_string(),
            created_at: now,
        })
    }

    pub fn list(conn: &Connection) -> Result<Vec<Project>> {
        let mut stmt = conn.prepare(
            "SELECT id, name, path, cloned_from, base_branch, created_at FROM projects ORDER BY created_at DESC",
        )?;
        let rows = stmt.query_map([], |row| {
            Ok(Project {
                id: row.get(0)?,
                name: row.get(1)?,
                path: row.get(2)?,
                cloned_from: row.get(3)?,
                base_branch: row.get(4)?,
                created_at: row.get(5)?,
            })
        })?;
        rows.collect()
    }

    pub fn get_by_id(conn: &Connection, id: &str) -> Result<Project> {
        conn.query_row(
            "SELECT id, name, path, cloned_from, base_branch, created_at FROM projects WHERE id = ?1",
            params![id],
            |row| {
                Ok(Project {
                    id: row.get(0)?,
                    name: row.get(1)?,
                    path: row.get(2)?,
                    cloned_from: row.get(3)?,
                    base_branch: row.get(4)?,
                    created_at: row.get(5)?,
                })
            },
        )
    }

    pub fn update(conn: &Connection, id: &str, name: &str, base_branch: &str) -> Result<()> {
        let affected = conn.execute(
            "UPDATE projects SET name = ?1, base_branch = ?2 WHERE id = ?3",
            params![name, base_branch, id],
        )?;
        if affected == 0 {
            return Err(rusqlite::Error::QueryReturnedNoRows);
        }
        Ok(())
    }

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

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

    fn setup() -> Connection {
        db::init_in_memory().expect("db init should succeed")
    }

    #[test]
    fn test_insert_project_local_path() {
        let conn = setup();
        let project = Project::insert(&conn, "My Project", "/home/user/code/myproject", None, "main")
            .expect("insert should succeed");

        assert_eq!(project.name, "My Project");
        assert_eq!(project.path, "/home/user/code/myproject");
        assert!(project.cloned_from.is_none());
        assert_eq!(project.base_branch, "main");
        assert!(!project.id.is_empty());
        assert!(!project.created_at.is_empty());
    }

    #[test]
    fn test_insert_project_cloned() {
        let conn = setup();
        let project = Project::insert(
            &conn,
            "Cloned Project",
            "/home/user/code/cloned",
            Some("https://github.com/org/repo.git"),
            "stable",
        )
        .expect("insert should succeed");

        assert_eq!(project.cloned_from.as_deref(), Some("https://github.com/org/repo.git"));
        assert_eq!(project.base_branch, "stable");
    }

    #[test]
    fn test_list_projects_empty() {
        let conn = setup();
        let projects = Project::list(&conn).expect("list should succeed");
        assert!(projects.is_empty());
    }

    #[test]
    fn test_list_projects_returns_all() {
        let conn = setup();
        Project::insert(&conn, "A", "/path/a", None, "main").unwrap();
        Project::insert(&conn, "B", "/path/b", None, "main").unwrap();

        let projects = Project::list(&conn).expect("list should succeed");
        assert_eq!(projects.len(), 2);
    }

    #[test]
    fn test_get_by_id() {
        let conn = setup();
        let created = Project::insert(&conn, "Test", "/path/test", None, "main").unwrap();
        let found = Project::get_by_id(&conn, &created.id).expect("get should succeed");

        assert_eq!(found.id, created.id);
        assert_eq!(found.name, "Test");
    }

    #[test]
    fn test_get_by_id_not_found() {
        let conn = setup();
        let result = Project::get_by_id(&conn, "nonexistent");
        assert!(result.is_err());
    }

    #[test]
    fn test_update_project() {
        let conn = setup();
        let created = Project::insert(&conn, "Old Name", "/path", None, "main").unwrap();

        Project::update(&conn, &created.id, "New Name", "develop").expect("update should succeed");

        let updated = Project::get_by_id(&conn, &created.id).unwrap();
        assert_eq!(updated.name, "New Name");
        assert_eq!(updated.base_branch, "develop");
    }

    #[test]
    fn test_delete_project() {
        let conn = setup();
        let created = Project::insert(&conn, "ToDelete", "/path", None, "main").unwrap();

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

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