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

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

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

impl TuleapCredentials {
    pub fn validate_input(
        tuleap_url: &str,
        username: &str,
        password: &str,
    ) -> std::result::Result<(String, String, String), String> {
        let normalized_url = tuleap_url.trim().trim_end_matches('/').to_string();
        if normalized_url.is_empty() {
            return Err("Tuleap URL cannot be empty".to_string());
        }

        let parsed_url = reqwest::Url::parse(&normalized_url)
            .map_err(|_| "Tuleap URL must be a valid absolute URL".to_string())?;

        if !matches!(parsed_url.scheme(), "http" | "https") {
            return Err("Tuleap URL must use http or https".to_string());
        }

        if parsed_url.host_str().is_none() {
            return Err("Tuleap URL must include a host".to_string());
        }

        let normalized_username = username.trim().to_string();
        if normalized_username.is_empty() {
            return Err("Username cannot be empty".to_string());
        }

        if password.trim().is_empty() {
            return Err("Password cannot be empty".to_string());
        }

        Ok((normalized_url, normalized_username, password.to_string()))
    }

    pub fn upsert_for_scope(
        conn: &Connection,
        project_id: Option<&str>,
        tuleap_url: &str,
        username: &str,
        password_encrypted: &str,
    ) -> Result<TuleapCredentials> {
        let id = Uuid::new_v4().to_string();

        match project_id {
            Some(project_id) => {
                conn.execute(
                    "DELETE FROM tuleap_credentials WHERE project_id = ?1",
                    params![project_id],
                )?;

                conn.execute(
                    "INSERT INTO tuleap_credentials (id, project_id, tuleap_url, username, password_encrypted) VALUES (?1, ?2, ?3, ?4, ?5)",
                    params![id, project_id, tuleap_url, username, password_encrypted],
                )?;
            }
            None => {
                conn.execute(
                    "DELETE FROM tuleap_credentials WHERE project_id IS NULL",
                    [],
                )?;

                conn.execute(
                    "INSERT INTO tuleap_credentials (id, project_id, tuleap_url, username, password_encrypted) VALUES (?1, NULL, ?2, ?3, ?4)",
                    params![id, tuleap_url, username, password_encrypted],
                )?;
            }
        }

        Ok(TuleapCredentials {
            id,
            project_id: project_id.map(String::from),
            tuleap_url: tuleap_url.to_string(),
            username: username.to_string(),
            password_encrypted: password_encrypted.to_string(),
        })
    }

    pub fn get_by_scope(
        conn: &Connection,
        project_id: Option<&str>,
    ) -> Result<Option<TuleapCredentials>> {
        match project_id {
            Some(project_id) => conn
                .query_row(
                    "SELECT id, project_id, tuleap_url, username, password_encrypted \
                     FROM tuleap_credentials \
                     WHERE project_id = ?1 \
                     LIMIT 1",
                    params![project_id],
                    |row| {
                        Ok(TuleapCredentials {
                            id: row.get(0)?,
                            project_id: row.get(1)?,
                            tuleap_url: row.get(2)?,
                            username: row.get(3)?,
                            password_encrypted: row.get(4)?,
                        })
                    },
                )
                .optional(),
            None => conn
                .query_row(
                    "SELECT id, project_id, tuleap_url, username, password_encrypted \
                     FROM tuleap_credentials \
                     WHERE project_id IS NULL \
                     LIMIT 1",
                    [],
                    |row| {
                        Ok(TuleapCredentials {
                            id: row.get(0)?,
                            project_id: row.get(1)?,
                            tuleap_url: row.get(2)?,
                            username: row.get(3)?,
                            password_encrypted: row.get(4)?,
                        })
                    },
                )
                .optional(),
        }
    }

    pub fn get_global(conn: &Connection) -> Result<Option<TuleapCredentials>> {
        Self::get_by_scope(conn, None)
    }

    pub fn get_for_project(
        conn: &Connection,
        project_id: &str,
    ) -> Result<Option<TuleapCredentials>> {
        if let Some(project_scoped) = Self::get_by_scope(conn, Some(project_id))? {
            return Ok(Some(project_scoped));
        }

        Self::get_global(conn)
    }

    pub fn delete_for_scope(conn: &Connection, project_id: Option<&str>) -> Result<()> {
        match project_id {
            Some(project_id) => {
                conn.execute(
                    "DELETE FROM tuleap_credentials WHERE project_id = ?1",
                    params![project_id],
                )?;
            }
            None => {
                conn.execute(
                    "DELETE FROM tuleap_credentials WHERE project_id IS NULL",
                    [],
                )?;
            }
        }

        Ok(())
    }

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

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

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

    fn create_project(conn: &Connection, name: &str) -> Project {
        Project::insert(conn, name, &format!("/tmp/{}", name), None, "main")
            .expect("project insert should succeed")
    }

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

        assert_eq!(creds.project_id, None);
        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_for_scope_replaces_only_target_scope() {
        let conn = setup();
        let project_a = create_project(&conn, "project-a");
        let project_b = create_project(&conn, "project-b");

        TuleapCredentials::upsert_for_scope(
            &conn,
            Some(&project_a.id),
            "https://a.example.com",
            "alice",
            "a1",
        )
        .expect("upsert for project A should succeed");

        TuleapCredentials::upsert_for_scope(
            &conn,
            Some(&project_b.id),
            "https://b.example.com",
            "bob",
            "b1",
        )
        .expect("upsert for project B should succeed");

        TuleapCredentials::upsert_for_scope(
            &conn,
            Some(&project_a.id),
            "https://a2.example.com",
            "alice2",
            "a2",
        )
        .expect("second upsert for project A should succeed");

        let creds_a = TuleapCredentials::get_by_scope(&conn, Some(&project_a.id))
            .expect("get project A should succeed")
            .expect("project A should have credentials");
        let creds_b = TuleapCredentials::get_by_scope(&conn, Some(&project_b.id))
            .expect("get project B should succeed")
            .expect("project B should have credentials");

        assert_eq!(creds_a.tuleap_url, "https://a2.example.com");
        assert_eq!(creds_a.username, "alice2");
        assert_eq!(creds_b.tuleap_url, "https://b.example.com");
        assert_eq!(creds_b.username, "bob");
    }

    #[test]
    fn test_get_for_project_falls_back_to_global() {
        let conn = setup();
        let project = create_project(&conn, "project-with-fallback");

        TuleapCredentials::upsert_for_scope(
            &conn,
            None,
            "https://global.example.com",
            "global",
            "enc",
        )
        .expect("global upsert should succeed");

        let creds = TuleapCredentials::get_for_project(&conn, &project.id)
            .expect("get for project should succeed")
            .expect("fallback global credentials should be returned");

        assert_eq!(creds.project_id, None);
        assert_eq!(creds.tuleap_url, "https://global.example.com");
    }

    #[test]
    fn test_get_for_project_prefers_scoped_credentials() {
        let conn = setup();
        let project = create_project(&conn, "project-scoped");

        TuleapCredentials::upsert_for_scope(
            &conn,
            None,
            "https://global.example.com",
            "global",
            "global",
        )
        .expect("global upsert should succeed");

        TuleapCredentials::upsert_for_scope(
            &conn,
            Some(&project.id),
            "https://project.example.com",
            "project-user",
            "project-enc",
        )
        .expect("project scoped upsert should succeed");

        let creds = TuleapCredentials::get_for_project(&conn, &project.id)
            .expect("get for project should succeed")
            .expect("project credentials should exist");

        assert_eq!(creds.project_id, Some(project.id));
        assert_eq!(creds.tuleap_url, "https://project.example.com");
        assert_eq!(creds.username, "project-user");
    }

    #[test]
    fn test_delete_for_scope_removes_only_target_scope() {
        let conn = setup();
        let project = create_project(&conn, "project-delete");

        TuleapCredentials::upsert_for_scope(
            &conn,
            None,
            "https://global.example.com",
            "global",
            "enc",
        )
        .expect("global upsert should succeed");
        TuleapCredentials::upsert_for_scope(
            &conn,
            Some(&project.id),
            "https://project.example.com",
            "project-user",
            "project-enc",
        )
        .expect("project scoped upsert should succeed");

        TuleapCredentials::delete_for_scope(&conn, Some(&project.id))
            .expect("delete for project scope should succeed");

        let scoped = TuleapCredentials::get_by_scope(&conn, Some(&project.id))
            .expect("scoped fetch should succeed");
        let global = TuleapCredentials::get_global(&conn).expect("global fetch should succeed");

        assert!(scoped.is_none());
        assert!(global.is_some());
    }

    #[test]
    fn test_validate_input_trims_url_and_username() {
        let (url, username, password) =
            TuleapCredentials::validate_input(" https://tuleap.example.com/ ", " alice ", "secret")
                .expect("validation should succeed");

        assert_eq!(url, "https://tuleap.example.com");
        assert_eq!(username, "alice");
        assert_eq!(password, "secret");
    }

    #[test]
    fn test_validate_input_rejects_empty_url() {
        let result = TuleapCredentials::validate_input("   ", "alice", "secret");
        assert!(result.is_err());
    }

    #[test]
    fn test_validate_input_rejects_invalid_url() {
        let result = TuleapCredentials::validate_input("not-an-url", "alice", "secret");
        assert!(result.is_err());
    }

    #[test]
    fn test_validate_input_rejects_non_http_scheme() {
        let result =
            TuleapCredentials::validate_input("ftp://tuleap.example.com", "alice", "secret");
        assert!(result.is_err());
    }

    #[test]
    fn test_validate_input_rejects_empty_username() {
        let result =
            TuleapCredentials::validate_input("https://tuleap.example.com", "   ", "secret");
        assert!(result.is_err());
    }

    #[test]
    fn test_validate_input_rejects_empty_password() {
        let result =
            TuleapCredentials::validate_input("https://tuleap.example.com", "alice", "   ");
        assert!(result.is_err());
    }
}
