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

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DeployInfo {
    pub id: String,
    pub project_id: String,
    pub server_host: Option<String>,
    pub server_port: Option<String>,
    pub username: Option<String>,
    pub password: Option<String>,
    pub ssh_key_path: Option<String>,
    pub quick_links: Option<String>, // JSON: [{label, url}]
    pub notes: Option<String>,
    pub updated_at: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct SaveDeployInfoInput {
    pub server_host: Option<String>,
    pub server_port: Option<String>,
    pub username: Option<String>,
    pub password: Option<String>,
    pub ssh_key_path: Option<String>,
    pub quick_links: Option<String>,
    pub notes: Option<String>,
}

// ── 业务函数 ──────────────────────────────────────────────────────────────────

pub fn get_info(conn: &Connection, project_id: &str) -> Option<DeployInfo> {
    conn.query_row(
        "SELECT id, project_id, server_host, server_port, username, password,
                ssh_key_path, quick_links, notes, updated_at
         FROM project_deploy_info WHERE project_id = ?1",
        params![project_id],
        |row| {
            Ok(DeployInfo {
                id:           row.get(0)?,
                project_id:   row.get(1)?,
                server_host:  row.get(2)?,
                server_port:  row.get(3)?,
                username:     row.get(4)?,
                password:     row.get(5)?,
                ssh_key_path: row.get(6)?,
                quick_links:  row.get(7)?,
                notes:        row.get(8)?,
                updated_at:   row.get(9)?,
            })
        },
    )
    .ok()
}

pub fn save_info(
    conn: &Connection,
    project_id: &str,
    input: &SaveDeployInfoInput,
) -> Result<DeployInfo, String> {
    let existing = get_info(conn, project_id);
    let id = existing
        .as_ref()
        .map(|e| e.id.clone())
        .unwrap_or_else(|| format!("deploy-{}", uuid::Uuid::new_v4()));

    conn.execute(
        "INSERT INTO project_deploy_info
            (id, project_id, server_host, server_port, username, password,
             ssh_key_path, quick_links, notes, updated_at)
         VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, datetime('now'))
         ON CONFLICT(project_id) DO UPDATE SET
            server_host  = ?3,
            server_port  = ?4,
            username     = ?5,
            password     = ?6,
            ssh_key_path = ?7,
            quick_links  = ?8,
            notes        = ?9,
            updated_at   = datetime('now')",
        params![
            id,
            project_id,
            input.server_host,
            input.server_port,
            input.username,
            input.password,
            input.ssh_key_path,
            input.quick_links,
            input.notes,
        ],
    )
    .map_err(|e| e.to_string())?;

    get_info(conn, project_id).ok_or_else(|| "保存后查询失败".to_string())
}

// ── Tauri command 薄包装层 ────────────────────────────────────────────────────

#[tauri::command]
pub fn get_deploy_info(project_id: String) -> Result<Option<DeployInfo>, String> {
    let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
    Ok(get_info(&conn, &project_id))
}

#[tauri::command]
pub fn save_deploy_info(
    project_id: String,
    input: SaveDeployInfoInput,
) -> Result<DeployInfo, String> {
    let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
    save_info(&conn, &project_id, &input)
}

// ── 单元测试 ──────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use rusqlite::Connection;

    fn setup() -> Connection {
        let conn = Connection::open_in_memory().unwrap();
        conn.execute_batch(
            "CREATE TABLE project_deploy_info (
                id          TEXT PRIMARY KEY,
                project_id  TEXT NOT NULL UNIQUE,
                server_host TEXT,
                server_port TEXT DEFAULT '22',
                username    TEXT,
                password    TEXT,
                ssh_key_path TEXT,
                quick_links TEXT,
                notes       TEXT,
                updated_at  TEXT DEFAULT (datetime('now'))
            );",
        )
        .unwrap();
        conn
    }

    #[test]
    fn test_get_empty() {
        let conn = setup();
        assert!(get_info(&conn, "proj-1").is_none());
    }

    #[test]
    fn test_save_and_get() {
        let conn = setup();
        let input = SaveDeployInfoInput {
            server_host: Some("192.168.1.100".to_string()),
            server_port: Some("22".to_string()),
            username: Some("root".to_string()),
            password: Some("secret".to_string()),
            ssh_key_path: None,
            quick_links: Some(r#"[{"label":"控制台","url":"https://example.com"}]"#.to_string()),
            notes: Some("测试服务器".to_string()),
        };
        let info = save_info(&conn, "proj-1", &input).unwrap();
        assert_eq!(info.server_host.as_deref(), Some("192.168.1.100"));
        assert_eq!(info.username.as_deref(), Some("root"));

        let fetched = get_info(&conn, "proj-1").unwrap();
        assert_eq!(fetched.password.as_deref(), Some("secret"));
    }

    #[test]
    fn test_upsert_overwrites() {
        let conn = setup();
        let input1 = SaveDeployInfoInput {
            server_host: Some("1.1.1.1".to_string()),
            server_port: Some("22".to_string()),
            username: Some("admin".to_string()),
            password: None,
            ssh_key_path: None,
            quick_links: None,
            notes: None,
        };
        save_info(&conn, "proj-1", &input1).unwrap();

        let input2 = SaveDeployInfoInput {
            server_host: Some("2.2.2.2".to_string()),
            server_port: Some("2222".to_string()),
            username: Some("deploy".to_string()),
            password: Some("new-pass".to_string()),
            ssh_key_path: None,
            quick_links: None,
            notes: None,
        };
        let info = save_info(&conn, "proj-1", &input2).unwrap();
        assert_eq!(info.server_host.as_deref(), Some("2.2.2.2"));
        assert_eq!(info.username.as_deref(), Some("deploy"));
    }
}
