use crate::models::{Connection, ConnectionInput};
use crate::store::Store;
use std::sync::Mutex;

const VALID_DB_TYPES: [&str; 4] = ["postgresql", "mysql", "sqlite", "redis"];

fn validate(input: &ConnectionInput) -> Result<(), String> {
    if input.name.is_empty() || input.name.chars().count() > 100 {
        return Err("name is required and must be 100 chars or fewer".into());
    }
    if !VALID_DB_TYPES.contains(&input.db_type.as_str()) {
        return Err(format!(
            "db_type must be one of: {}",
            VALID_DB_TYPES.join(", ")
        ));
    }
    if input.host.is_empty() || input.host.chars().count() > 255 {
        return Err("host is required and must be 255 chars or fewer".into());
    }
    if input.db_type != "sqlite" {
        match input.port {
            Some(p) if (1..=65535).contains(&p) => {}
            _ => return Err("port must be an integer between 1 and 65535 for this db_type".into()),
        }
    }
    if let Some(u) = &input.username {
        if u.chars().count() > 100 {
            return Err("username must be 100 chars or fewer".into());
        }
    }
    Ok(())
}

pub fn get_connections_inner(state: &Mutex<Store>) -> Result<Vec<Connection>, String> {
    let store = state.lock().map_err(|e| e.to_string())?;
    store.get_connections()
}

pub fn create_connection_inner(
    state: &Mutex<Store>,
    input: ConnectionInput,
) -> Result<Connection, String> {
    validate(&input)?;
    let store = state.lock().map_err(|e| e.to_string())?;
    store.create_connection(input)
}

pub fn update_connection_inner(
    state: &Mutex<Store>,
    id: String,
    input: ConnectionInput,
) -> Result<Connection, String> {
    validate(&input)?;
    let store = state.lock().map_err(|e| e.to_string())?;
    store.update_connection(&id, input)
}

pub fn delete_connection_inner(state: &Mutex<Store>, id: &str) -> Result<(), String> {
    let store = state.lock().map_err(|e| e.to_string())?;
    store.delete_connection(id)
}

pub fn add_connection_tags_inner(
    state: &Mutex<Store>,
    connection_id: String,
    tag_ids: Vec<String>,
) -> Result<(), String> {
    let store = state.lock().map_err(|e| e.to_string())?;
    store.add_connection_tags(&connection_id, &tag_ids)
}

#[tauri::command]
pub fn get_connections(state: tauri::State<crate::AppState>) -> Result<Vec<Connection>, String> {
    get_connections_inner(&state.db_store)
}

#[tauri::command]
pub fn create_connection(
    state: tauri::State<crate::AppState>,
    input: ConnectionInput,
) -> Result<Connection, String> {
    create_connection_inner(&state.db_store, input)
}

#[tauri::command]
pub fn update_connection(
    state: tauri::State<crate::AppState>,
    id: String,
    input: ConnectionInput,
) -> Result<Connection, String> {
    update_connection_inner(&state.db_store, id, input)
}

#[tauri::command]
pub fn delete_connection(
    state: tauri::State<crate::AppState>,
    id: String,
    app: tauri::AppHandle,
) -> Result<(), String> {
    delete_connection_inner(&state.db_store, &id)?;
    // Purge keychain secrets (missing entries are no-ops) and close any
    // SSH tunnel associated with the deleted connection.
    let _ = crate::commands::keychain::delete_connection_password_internal(&app, &id);
    let _ = crate::commands::keychain::delete_connection_ssh_password_internal(&app, &id);
    let _ = crate::commands::keychain::delete_connection_ssh_passphrase_internal(&app, &id);
    if let Ok(mut mgr) = state.ssh_manager.lock() {
        mgr.close_tunnel(&id);
    }
    Ok(())
}

#[tauri::command]
pub fn add_connection_tags(
    state: tauri::State<crate::AppState>,
    connection_id: String,
    tag_ids: Vec<String>,
) -> Result<(), String> {
    add_connection_tags_inner(&state.db_store, connection_id, tag_ids)
}

pub fn set_connection_favorite_inner(
    state: &Mutex<Store>,
    connection_id: String,
    favorite: bool,
) -> Result<(), String> {
    let store = state.lock().map_err(|e| e.to_string())?;
    store.set_connection_favorite(&connection_id, favorite)
}

pub fn record_recent_connection_inner(
    state: &Mutex<Store>,
    connection_id: String,
) -> Result<(), String> {
    let store = state.lock().map_err(|e| e.to_string())?;
    store.record_recent_connection(&connection_id)
}

pub fn get_recent_connections_inner(
    state: &Mutex<Store>,
    limit: i64,
) -> Result<Vec<crate::models::RecentConnection>, String> {
    let store = state.lock().map_err(|e| e.to_string())?;
    store.get_recent_connections(limit)
}

pub fn clear_recent_connections_inner(state: &Mutex<Store>) -> Result<(), String> {
    let store = state.lock().map_err(|e| e.to_string())?;
    store.clear_recent_connections()
}

#[tauri::command]
pub fn set_connection_favorite(
    state: tauri::State<crate::AppState>,
    connection_id: String,
    favorite: bool,
) -> Result<(), String> {
    set_connection_favorite_inner(&state.db_store, connection_id, favorite)
}

#[tauri::command]
pub fn record_recent_connection(
    state: tauri::State<crate::AppState>,
    connection_id: String,
) -> Result<(), String> {
    record_recent_connection_inner(&state.db_store, connection_id)
}

#[tauri::command]
pub fn get_recent_connections(
    state: tauri::State<crate::AppState>,
    limit: i64,
) -> Result<Vec<crate::models::RecentConnection>, String> {
    get_recent_connections_inner(&state.db_store, limit)
}

#[tauri::command]
pub fn clear_recent_connections(state: tauri::State<crate::AppState>) -> Result<(), String> {
    clear_recent_connections_inner(&state.db_store)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::models::ConnectionInput;
    use crate::store::Store;

    fn state() -> std::sync::Mutex<Store> {
        let conn = rusqlite::Connection::open_in_memory().unwrap();
        crate::store::migrations::run_migrations(&conn).unwrap();
        std::sync::Mutex::new(Store::from_connection(conn))
    }

    #[test]
    fn get_connections_returns_list() {
        let st = state();
        let result = get_connections_inner(&st);
        assert!(result.is_ok());
        assert_eq!(result.unwrap().len(), 0);
    }

    #[test]
    fn create_connection_command_returns_connection() {
        let st = state();
        let input = ConnectionInput {
            name: "Prod".into(),
            db_type: "postgresql".into(),
            host: "h".into(),
            port: Some(5432),
            username: None,
            folder_id: None,
            password: None,
            database: None,
            environment: None,
            ssh_host: None,
            ssh_port: None,
            ssh_user: None,
            ssh_auth_method: None,
            ssh_private_key_path: None,
            ssh_password: None,
            ssh_passphrase: None,
            ssl_mode: None,
            ssl_ca_path: None,
            ssl_cert_path: None,
            ssl_key_path: None,
            tag_ids: vec![],
            use_keychain: true,
        };
        let result = create_connection_inner(&st, input.clone()).unwrap();
        assert_eq!(result.name, "Prod");
        assert_eq!(get_connections_inner(&st).unwrap().len(), 1);
    }

    #[test]
    fn create_connection_rejects_invalid_db_type() {
        let st = state();
        let input = ConnectionInput {
            name: "X".into(),
            db_type: "mongodb".into(),
            host: "h".into(),
            port: Some(5432),
            username: None,
            folder_id: None,
            password: None,
            database: None,
            environment: None,
            ssh_host: None,
            ssh_port: None,
            ssh_user: None,
            ssh_auth_method: None,
            ssh_private_key_path: None,
            ssh_password: None,
            ssh_passphrase: None,
            ssl_mode: None,
            ssl_ca_path: None,
            ssl_cert_path: None,
            ssl_key_path: None,
            tag_ids: vec![],
            use_keychain: true,
        };
        assert!(create_connection_inner(&st, input).is_err());
    }

    #[test]
    fn delete_connection_command_removes_it() {
        let st = state();
        let input = ConnectionInput {
            name: "X".into(),
            db_type: "postgresql".into(),
            host: "h".into(),
            port: Some(5432),
            username: None,
            folder_id: None,
            password: None,
            database: None,
            environment: None,
            ssh_host: None,
            ssh_port: None,
            ssh_user: None,
            ssh_auth_method: None,
            ssh_private_key_path: None,
            ssh_password: None,
            ssh_passphrase: None,
            ssl_mode: None,
            ssl_ca_path: None,
            ssl_cert_path: None,
            ssl_key_path: None,
            tag_ids: vec![],
            use_keychain: true,
        };
        let conn = create_connection_inner(&st, input).unwrap();
        delete_connection_inner(&st, &conn.id).unwrap();
        assert_eq!(get_connections_inner(&st).unwrap().len(), 0);
    }

    #[test]
    fn set_connection_favorite_command_persists() {
        let st = state();
        let input = ConnectionInput {
            name: "P".into(),
            db_type: "postgresql".into(),
            host: "h".into(),
            port: Some(5432),
            username: None,
            folder_id: None,
            tag_ids: vec![],
            use_keychain: true,
            password: None,
            database: None,
            environment: None,
            ssh_host: None,
            ssh_port: None,
            ssh_user: None,
            ssh_auth_method: None,
            ssh_private_key_path: None,
            ssh_password: None,
            ssh_passphrase: None,
            ssl_mode: None,
            ssl_ca_path: None,
            ssl_cert_path: None,
            ssl_key_path: None,
        };
        let conn = create_connection_inner(&st, input).unwrap();
        set_connection_favorite_inner(&st, conn.id.clone(), true).unwrap();
        assert_eq!(get_connections_inner(&st).unwrap()[0].favorite, true);
    }

    #[test]
    fn record_recent_command_upserts() {
        let st = state();
        let input = ConnectionInput {
            name: "P".into(),
            db_type: "postgresql".into(),
            host: "h".into(),
            port: Some(5432),
            username: None,
            folder_id: None,
            tag_ids: vec![],
            use_keychain: true,
            password: None,
            database: None,
            environment: None,
            ssh_host: None,
            ssh_port: None,
            ssh_user: None,
            ssh_auth_method: None,
            ssh_private_key_path: None,
            ssh_password: None,
            ssh_passphrase: None,
            ssl_mode: None,
            ssl_ca_path: None,
            ssl_cert_path: None,
            ssl_key_path: None,
        };
        let conn = create_connection_inner(&st, input).unwrap();
        record_recent_connection_inner(&st, conn.id.clone()).unwrap();
        record_recent_connection_inner(&st, conn.id.clone()).unwrap(); // idempotent upsert
        let recent = get_recent_connections_inner(&st, 10).unwrap();
        assert_eq!(recent.len(), 1);
        clear_recent_connections_inner(&st).unwrap();
        assert_eq!(get_recent_connections_inner(&st, 10).unwrap().len(), 0);
    }
}
