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

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TuleapCredentials {
    pub id: String,
    pub tuleap_url: String,
    pub username: String,
    pub password_encrypted: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TuleapCredentialsSafe {
    pub id: String,
    pub tuleap_url: String,
    pub username: String,
}

impl TuleapCredentials {
    pub fn upsert(
        conn: &Connection,
        tuleap_url: &str,
        username: &str,
        password_encrypted: &str,
    ) -> Result<TuleapCredentials> {
        conn.execute("DELETE FROM tuleap_credentials", [])?;

        let id = Uuid::new_v4().to_string();
        conn.execute(
            "INSERT INTO tuleap_credentials (id, tuleap_url, username, password_encrypted) VALUES (?1, ?2, ?3, ?4)",
            params![id, tuleap_url, username, password_encrypted],
        )?;

        Ok(TuleapCredentials {
            id,
            tuleap_url: tuleap_url.to_string(),
            username: username.to_string(),
            password_encrypted: password_encrypted.to_string(),
        })
    }

    pub fn get(conn: &Connection) -> Result<Option<TuleapCredentials>> {
        let mut stmt = conn.prepare(
            "SELECT id, tuleap_url, username, password_encrypted FROM tuleap_credentials LIMIT 1",
        )?;
        let mut rows = stmt.query_map([], |row| {
            Ok(TuleapCredentials {
                id: row.get(0)?,
                tuleap_url: row.get(1)?,
                username: row.get(2)?,
                password_encrypted: row.get(3)?,
            })
        })?;

        match rows.next() {
            Some(row) => Ok(Some(row?)),
            None => Ok(None),
        }
    }

    pub fn delete(conn: &Connection) -> Result<()> {
        conn.execute("DELETE FROM tuleap_credentials", [])?;
        Ok(())
    }

    pub fn to_safe(&self) -> TuleapCredentialsSafe {
        TuleapCredentialsSafe {
            id: self.id.clone(),
            tuleap_url: self.tuleap_url.clone(),
            username: self.username.clone(),
        }
    }
}

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

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

    #[test]
    fn test_upsert_creates_credentials() {
        let conn = setup();
        let creds = TuleapCredentials::upsert(
            &conn,
            "https://tuleap.example.com",
            "alice",
            "encrypted_password",
        )
        .expect("upsert should succeed");

        assert_eq!(creds.tuleap_url, "https://tuleap.example.com");
        assert_eq!(creds.username, "alice");
        assert_eq!(creds.password_encrypted, "encrypted_password");
        assert!(!creds.id.is_empty());
    }

    #[test]
    fn test_upsert_replaces_existing() {
        let conn = setup();
        TuleapCredentials::upsert(&conn, "https://old.example.com", "old_user", "old_enc")
            .expect("first upsert should succeed");

        let second = TuleapCredentials::upsert(
            &conn,
            "https://new.example.com",
            "new_user",
            "new_enc",
        )
        .expect("second upsert should succeed");

        // Only one record should exist
        let creds = TuleapCredentials::get(&conn)
            .expect("get should succeed")
            .expect("should have credentials");

        assert_eq!(creds.id, second.id);
        assert_eq!(creds.tuleap_url, "https://new.example.com");
        assert_eq!(creds.username, "new_user");
    }

    #[test]
    fn test_get_returns_none_when_empty() {
        let conn = setup();
        let result = TuleapCredentials::get(&conn).expect("get should succeed");
        assert!(result.is_none());
    }

    #[test]
    fn test_get_returns_credentials() {
        let conn = setup();
        let created = TuleapCredentials::upsert(
            &conn,
            "https://tuleap.example.com",
            "bob",
            "enc_pass",
        )
        .expect("upsert should succeed");

        let fetched = TuleapCredentials::get(&conn)
            .expect("get should succeed")
            .expect("should have credentials");

        assert_eq!(fetched.id, created.id);
        assert_eq!(fetched.tuleap_url, "https://tuleap.example.com");
        assert_eq!(fetched.username, "bob");
        assert_eq!(fetched.password_encrypted, "enc_pass");
    }

    #[test]
    fn test_delete_removes_credentials() {
        let conn = setup();
        TuleapCredentials::upsert(&conn, "https://tuleap.example.com", "carol", "enc")
            .expect("upsert should succeed");

        TuleapCredentials::delete(&conn).expect("delete should succeed");

        let result = TuleapCredentials::get(&conn).expect("get should succeed");
        assert!(result.is_none());
    }
}
