use std::io::Write as _;
use std::path::PathBuf;

use crate::openssh::MyPrefs;

pub fn write_source(prefs: MyPrefs) -> anyhow::Result<()> {
    let SrcPaths { src_dir, src_path } = src_path();

    // create dir
    if !src_dir.exists() {
        std::fs::create_dir_all(&src_dir)?;
    }

    // open file
    let mut file = std::fs::File::create(src_path)?;

    writeln!(
        file,
        r#"//! This file is autogenerated at build-time when `RELOAD_SSH_ALGO` is set to environment."#
    )?;
    writeln!(file)?;

    writeln!(file, "use crate::DefaultAlgorithms;")?;
    writeln!(file,)?;

    writeln!(file, r#"/// Default algorithms for ssh."#)?;
    writeln!(file, r#"///"#)?;
    write_algos_in_docs(&mut file, "Ciphers algorithms", &prefs.ciphers)?;
    write_algos_in_docs(
        &mut file,
        "Key Exchange (KEX) algorithms",
        &prefs.kex_algorithms,
    )?;
    write_algos_in_docs(&mut file, "Host Key algorithms", &prefs.host_key_algorithms)?;
    write_algos_in_docs(
        &mut file,
        "Message Authentication Code (MAC) algorithms",
        &prefs.mac,
    )?;
    write_algos_in_docs(
        &mut file,
        "CA Signature algorithms",
        &prefs.ca_signature_algorithms,
    )?;
    write_algos_in_docs(
        &mut file,
        "Public Key Accepted algorithms",
        &prefs.pubkey_accepted_algorithms,
    )?;
    writeln!(file, r#"pub fn defaults() -> DefaultAlgorithms {{"#)?;
    writeln!(file, r#"    DefaultAlgorithms {{"#)?;
    write_vec(
        &mut file,
        "ca_signature_algorithms",
        &prefs.ca_signature_algorithms,
    )?;
    write_vec(&mut file, "ciphers", &prefs.ciphers)?;
    write_vec(&mut file, "host_key_algorithms", &prefs.host_key_algorithms)?;
    write_vec(&mut file, "kex_algorithms", &prefs.kex_algorithms)?;
    write_vec(&mut file, "mac", &prefs.mac)?;
    write_vec(
        &mut file,
        "pubkey_accepted_algorithms",
        &prefs.pubkey_accepted_algorithms,
    )?;
    writeln!(file, r#"    }}"#)?;
    writeln!(file, r#"}}"#)?;

    Ok(())
}

fn write_vec(file: &mut std::fs::File, name: &str, vec: &[String]) -> anyhow::Result<()> {
    writeln!(file, r#"        {name}: vec!["#)?;
    for item in vec {
        writeln!(file, r#"            "{item}".to_string(),"#,)?;
    }
    writeln!(file, r#"        ],"#)?;
    Ok(())
}

/// Writes algorithms in documentation format
fn write_algos_in_docs(
    file: &mut std::fs::File,
    group: &str,
    algos: &[String],
) -> anyhow::Result<()> {
    writeln!(file, "/// ## {group}")?;
    writeln!(file, "///")?;
    for algo in algos {
        writeln!(file, "/// - `{algo}`", algo = algo.trim_matches('"'))?;
    }
    writeln!(file, "///")?;

    Ok(())
}

struct SrcPaths {
    src_dir: PathBuf,
    src_path: PathBuf,
}

fn src_path() -> SrcPaths {
    let src_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("src")
        .join("default_algorithms");
    let src_path = src_dir.join("openssh.rs");

    SrcPaths { src_dir, src_path }
}
