volli 0.1.10

CLI frontend for volli
use crate::commands::utils::{export_path, show_profile};
use crate::{ProfileCommand, ProfileOpts};
use eyre::eyre;

pub fn run(opts: ProfileOpts) {
    match opts.command {
        ProfileCommand::List => {
            let serve = volli_server::list_profiles().unwrap_or_default();
            let agent = volli_agent::list_profiles().unwrap_or_default();
            println!("serve profiles:");
            for p in serve {
                println!("  {p}");
            }
            println!("agent profiles:");
            for p in agent {
                println!("  {p}");
            }
        }
        ProfileCommand::Show(show) => {
            show_profile(&show.profile, show.coord, show.agent);
        }
        ProfileCommand::Delete(del) => {
            let mut confirmed = false;
            if !del.agent && !del.serve {
                println!(
                    "Delete both agent and serve profile '{}' ? [y/N]",
                    del.profile
                );
                let mut c = String::new();
                std::io::stdin().read_line(&mut c).ok();
                confirmed = c.trim() == "y";
            }
            if del.agent || confirmed && !del.serve {
                volli_agent::delete_profile(&del.profile).ok();
            }
            if del.agent || !del.serve && confirmed {
                volli_server::delete_profile(&del.profile).ok();
            }
        }
        ProfileCommand::Rename(rn) => {
            let agent_exists = volli_agent::profile_exists(&rn.old);
            let coord_exists = volli_server::profile_exists(&rn.old);
            let which = if rn.coord {
                Some("coord")
            } else if rn.agent {
                Some("agent")
            } else {
                None
            };
            let res = match which {
                Some("coord") => volli_server::rename_profile(&rn.old, &rn.new),
                Some("agent") => volli_agent::rename_profile(&rn.old, &rn.new),
                None => {
                    if agent_exists && coord_exists {
                        Err(eyre!("profile name conflict; specify --coord or --agent"))
                    } else if agent_exists {
                        volli_agent::rename_profile(&rn.old, &rn.new)
                    } else if coord_exists {
                        volli_server::rename_profile(&rn.old, &rn.new)
                    } else {
                        Err(eyre!("profile not found"))
                    }
                }
                _ => unreachable!(),
            };
            if let Err(e) = res {
                eprintln!("rename error: {e}");
            }
        }
        ProfileCommand::Update(upd) => {
            if let Some(token) = upd.add_join_token {
                volli_server::add_join_host_from_token(&upd.profile, &token).ok();
            }
            if let Some(host) = upd.add_join_host {
                let entry = volli_server::JoinHostEntry {
                    coord_id: None,
                    host,
                    tcp_port: upd.join_tcp_port,
                    quic_port: upd.join_quic_port,
                    token: None,
                    cert: None,
                    fingerprint: None,
                    last_ok: None,
                    last_fail: None,
                };
                volli_server::add_join_host(&upd.profile, entry).ok();
            }
            if let Some(idx) = upd.remove_join_index {
                volli_server::remove_join_host_index(&upd.profile, idx).ok();
            }
            if let Some(host) = upd.bind_host {
                volli_server::save_bind_host(&upd.profile, &host).ok();
            }
            if let Some(p) = upd.tcp_port {
                volli_server::save_tcp_port(&upd.profile, p).ok();
            }
            if let Some(p) = upd.quic_port {
                volli_server::save_quic_port(&upd.profile, p).ok();
            }
            if let Some(host) = upd.advertise_host {
                volli_server::save_profile_host(&upd.profile, &host).ok();
            }
            if let Some(list) = upd.agent_whitelist {
                let vals: Vec<String> = list
                    .split(',')
                    .map(|s| s.trim().to_string())
                    .filter(|s| !s.is_empty())
                    .collect();
                volli_server::save_agent_whitelist(&upd.profile, &vals).ok();
            }
            if let Some(list) = upd.coord_whitelist {
                let vals: Vec<String> = list
                    .split(',')
                    .map(|s| s.trim().to_string())
                    .filter(|s| !s.is_empty())
                    .collect();
                volli_server::save_coord_whitelist(&upd.profile, &vals).ok();
            }
            if let Some(secret) = upd.agent_secret {
                volli_agent::save_state(&upd.profile, &secret).ok();
            }
        }
        ProfileCommand::Export(exp) => {
            let agent_exists = volli_agent::profile_exists(&exp.profile);
            let coord_exists = volli_server::profile_exists(&exp.profile);
            let which = if exp.coord {
                Some("coord")
            } else if exp.agent {
                Some("agent")
            } else {
                None
            };
            let yaml = match which {
                Some("coord") => volli_server::export_profile(&exp.profile).ok(),
                Some("agent") => volli_agent::export_profile(&exp.profile).ok(),
                None => {
                    if agent_exists && coord_exists {
                        eprintln!("profile name conflict; specify --coord or --agent");
                        None
                    } else if agent_exists {
                        volli_agent::export_profile(&exp.profile).ok()
                    } else if coord_exists {
                        volli_server::export_profile(&exp.profile).ok()
                    } else {
                        eprintln!("profile not found");
                        None
                    }
                }
                _ => None,
            };
            if let Some(data) = yaml {
                if exp.stdout {
                    print!("{data}");
                } else {
                    let file = export_path(&exp.profile, exp.output.as_deref());
                    std::fs::write(&file, data).ok();
                    println!("written {file}");
                }
            }
        }
        ProfileCommand::Import(imp) => {
            let data = std::fs::read_to_string(&imp.file).expect("read file");
            let kind: Result<volli_server::CoordProfileExport, _> = serde_yaml::from_str(&data);
            if kind.is_ok() {
                match volli_server::import_profile(&data, imp.name.as_deref(), imp.force) {
                    Ok(name) => println!("imported coordinator profile '{name}'"),
                    Err(e) => {
                        if e.to_string().contains("profile exists") && !imp.force {
                            println!("Profile exists. Overwrite? [y/N]");
                            let mut c = String::new();
                            std::io::stdin().read_line(&mut c).ok();
                            if c.trim().eq_ignore_ascii_case("y") {
                                match volli_server::import_profile(&data, imp.name.as_deref(), true)
                                {
                                    Ok(name) => println!("imported coordinator profile '{name}'"),
                                    Err(e) => eprintln!("import error: {e}"),
                                }
                            } else {
                                println!("aborted");
                            }
                        } else {
                            eprintln!("import error: {e}");
                        }
                    }
                }
            } else {
                match volli_agent::import_profile(&data, imp.name.as_deref(), imp.force) {
                    Ok(name) => println!("imported agent profile '{name}'"),
                    Err(e) => {
                        if e.to_string().contains("profile exists") && !imp.force {
                            println!("Profile exists. Overwrite? [y/N]");
                            let mut c = String::new();
                            std::io::stdin().read_line(&mut c).ok();
                            if c.trim().eq_ignore_ascii_case("y") {
                                match volli_agent::import_profile(&data, imp.name.as_deref(), true)
                                {
                                    Ok(name) => println!("imported agent profile '{name}'"),
                                    Err(e) => eprintln!("import error: {e}"),
                                }
                            } else {
                                println!("aborted");
                            }
                        } else {
                            eprintln!("import error: {e}");
                        }
                    }
                }
            }
        }
        ProfileCommand::Edit(edit) => {
            let agent_exists = volli_agent::profile_exists(&edit.profile);
            let coord_exists = volli_server::profile_exists(&edit.profile);
            let which = if edit.coord {
                Some("coord")
            } else if edit.agent {
                Some("agent")
            } else {
                None
            };
            let yaml_opt = match which {
                Some("coord") => volli_server::export_profile(&edit.profile).ok(),
                Some("agent") => volli_agent::export_profile(&edit.profile).ok(),
                None => {
                    if agent_exists && coord_exists {
                        eprintln!("profile name conflict; specify --coord or --agent");
                        None
                    } else if agent_exists {
                        volli_agent::export_profile(&edit.profile).ok()
                    } else if coord_exists {
                        volli_server::export_profile(&edit.profile).ok()
                    } else {
                        eprintln!("profile not found");
                        None
                    }
                }
                _ => None,
            };
            if let Some(mut data) = yaml_opt {
                let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vi".into());
                let tmp = tempfile::NamedTempFile::new().expect("tmpfile");
                std::fs::write(tmp.path(), &data).ok();
                if editor.split_whitespace().count() > 1 {
                    let cmd = format!("{} {}", editor, tmp.path().display());
                    std::process::Command::new("sh")
                        .arg("-c")
                        .arg(cmd)
                        .status()
                        .ok();
                } else {
                    std::process::Command::new(&editor)
                        .arg(tmp.path())
                        .status()
                        .ok();
                }
                data = std::fs::read_to_string(tmp.path()).unwrap_or(data);
                let valid = match which {
                    Some("coord") => {
                        serde_yaml::from_str::<volli_server::CoordProfileExport>(&data).is_ok()
                    }
                    Some("agent") => {
                        serde_yaml::from_str::<volli_agent::AgentProfileExport>(&data).is_ok()
                    }
                    None => {
                        if coord_exists {
                            serde_yaml::from_str::<volli_server::CoordProfileExport>(&data).is_ok()
                        } else {
                            serde_yaml::from_str::<volli_agent::AgentProfileExport>(&data).is_ok()
                        }
                    }
                    _ => false,
                };
                if valid {
                    if which == Some("coord") || (which.is_none() && coord_exists && !agent_exists)
                    {
                        if volli_server::import_profile(&data, Some(&edit.profile), true).is_ok() {
                            println!("updated coordinator profile '{}'", edit.profile);
                        }
                    } else if volli_agent::import_profile(&data, Some(&edit.profile), true).is_ok()
                    {
                        println!("updated agent profile '{}'", edit.profile);
                    }
                } else {
                    eprintln!("syntax error - not saving");
                }
            }
        }
    }
}