use clap::Parser;
use env_logger::Env;
use openssl::hash::MessageDigest;
use sh4d0wup::args::{self, Args, Hsm, HsmPgp, Keygen, Sign, SubCommand};
use sh4d0wup::bait;
use sh4d0wup::build;
use sh4d0wup::check;
use sh4d0wup::errors::*;
use sh4d0wup::hsm;
use sh4d0wup::infect;
use sh4d0wup::keygen;
use sh4d0wup::keygen::in_toto::InTotoEmbedded;
use sh4d0wup::keygen::openssl::OpensslEmbedded;
use sh4d0wup::keygen::pgp::PgpEmbedded;
use sh4d0wup::plot;
use sh4d0wup::req;
use sh4d0wup::sign;
use sh4d0wup::tamper;
use std::fs;
use std::io;
use std::io::Write;

#[tokio::main]
async fn main() -> Result<()> {
    let args = Args::parse();

    let log_level = match (args.quiet, args.verbose) {
        (0, 0) => "warn,sh4d0wup=info",
        (1, 0) => "warn",
        (_, 0) => "error",
        (_, 1) => "info,sh4d0wup=debug",
        (_, 2) => "debug",
        (_, 3) => "debug,sh4d0wup=trace",
        _ => "trace",
    };
    env_logger::init_from_env(Env::default().default_filter_or(log_level));

    match args.subcommand {
        SubCommand::Bait(bait) => {
            let ctx = bait.plot.load_into_context().await?;
            bait::run(ctx, &bait.bind, &bait.tls).await?;
        }
        SubCommand::Front(front) => {
            let plot = plot::Plot {
                upstreams: maplit::btreemap! {
                    "upstream".to_string() => plot::Upstream {
                        url: front.upstream,
                        keep_headers: false,
                    },
                },
                routes: vec![plot::Route {
                    path: None,
                    host: None,
                    selector: None,
                    action: plot::RouteAction::Proxy(plot::ProxyRoute {
                        upstream: "upstream".to_string(),
                        path: None,
                    }),
                }],
                ..Default::default()
            };

            let ctx = plot.resolve().await?;
            bait::run(ctx, &front.bind, &front.tls).await?
        }
        SubCommand::Infect(infect) => infect::run(infect).await?,
        SubCommand::Tamper(tamper) => tamper::run(tamper).await?,
        SubCommand::Check(check) => {
            let ctx = check.plot.load_into_context().await?;
            match check.no_exec {
                0 => {
                    check::spawn(check, ctx).await?;
                }
                1 => {
                    serde_json::to_writer_pretty(io::stdout(), &ctx.plot)?;
                    println!();
                }
                _ => {
                    serde_json::to_writer_pretty(io::stdout(), &ctx.plot)?;
                    println!();
                    for (key, value) in &ctx.extras.signing_keys {
                        println!("signing_key {key:?}: {value:?}");
                    }
                    for (key, value) in &ctx.extras.artifacts {
                        println!("artifact {:?}: {} bytes", key, value.len());
                    }
                }
            }
        }
        SubCommand::Keygen(Keygen::Tls(tls)) => {
            let flags = tls.flags.clone();
            let tls =
                keygen::tls::generate(tls.into()).context("Failed to generate tls certificate")?;

            if flags.public_key() {
                print!("{}", tls.cert);
            }

            if flags.secret_key() {
                print!("{}", tls.key);
            }
        }
        SubCommand::Keygen(Keygen::Pgp(pgp)) => {
            let flags = pgp.flags.clone();
            let pgp = keygen::pgp::generate(pgp.into()).context("Failed to generate pgp key")?;

            if flags.public_key()
                && let Some(cert) = pgp.cert
            {
                keygen::pgp::debug_inspect(cert.as_bytes())
                    .context("Failed to inspect serialized pgp data")?;
                print!("{cert}");
            }

            if flags.secret_key() {
                keygen::pgp::debug_inspect(pgp.secret_key.as_bytes())
                    .context("Failed to inspect serialized pgp data")?;
                print!("{}", pgp.secret_key);
            }

            if flags.all()
                && let Some(rev) = pgp.rev
            {
                keygen::pgp::debug_inspect(rev.as_bytes())
                    .context("Failed to inspect serialized pgp data")?;
                print!("{rev}");
            }
        }
        SubCommand::Keygen(Keygen::Ssh(ssh)) => {
            let flags = ssh.flags.clone();
            let ssh =
                keygen::ssh::generate(&ssh.try_into()?).context("Failed to generate ssh key")?;

            if flags.public_key()
                && let Some(public_key) = ssh.public_key
            {
                println!("{public_key}");
            }

            if flags.secret_key() {
                print!("{}", ssh.secret_key);
            }
        }
        SubCommand::Keygen(Keygen::Openssl(openssl)) => {
            let flags = openssl.flags.clone();
            let openssl = keygen::openssl::generate(&openssl.try_into()?)
                .context("Failed to generate openssl key")?;

            if flags.public_key()
                && let Some(public_key) = openssl.public_key
            {
                print!("{public_key}");
            }

            if flags.secret_key() {
                print!("{}", openssl.secret_key);
            }
        }
        SubCommand::Keygen(Keygen::InToto(in_toto)) => {
            let flags = in_toto.flags.clone();
            let in_toto = keygen::in_toto::generate(&in_toto.into())
                .context("Failed to generate in-toto key")?;

            if flags.public_key()
                && let Some(public_key) = in_toto.public_key
            {
                println!("{public_key}");
            }

            if flags.secret_key() {
                println!("{}", in_toto.secret_key);
            }
        }
        SubCommand::Sign(Sign::PgpCleartext(pgp)) => {
            if pgp.binary {
                bail!("Binary output is not supported for cleartext signatures");
            }
            let secret_key = PgpEmbedded::read_from_disk(&pgp.secret_key)
                .context("Failed to load secret key")?;
            debug!("Reading data to sign...");
            let data = fs::read(&pgp.path)
                .with_context(|| anyhow!("Failed to read payload data from {:?}", pgp.path))?;
            let sig = sign::pgp::sign_cleartext(&secret_key, &data)?;
            io::stdout().write_all(&sig)?;
        }
        SubCommand::Sign(Sign::PgpDetached(pgp)) => {
            let secret_key = PgpEmbedded::read_from_disk(&pgp.secret_key)
                .context("Failed to load secret key")?;
            debug!("Reading data to sign...");
            let data = fs::read(&pgp.path)
                .with_context(|| anyhow!("Failed to read payload data from {:?}", pgp.path))?;
            let sig = sign::pgp::sign_detached(&secret_key, &data, pgp.binary)?;
            io::stdout().write_all(&sig)?;
        }
        SubCommand::Sign(Sign::Openssl(openssl)) => {
            if !openssl.binary {
                bail!("Openssl signatures are always binary");
            }
            let secret_key = OpensslEmbedded::read_from_disk(&openssl.secret_key)
                .context("Failed to load secret key")?;
            debug!("Reading data to sign...");
            let data = fs::read(&openssl.path)
                .with_context(|| anyhow!("Failed to read payload data from {:?}", openssl.path))?;

            let digest = if openssl.md5 {
                MessageDigest::md5()
            } else if openssl.sha1 {
                MessageDigest::sha1()
            } else if openssl.sha256 {
                MessageDigest::sha256()
            } else if openssl.sha512 {
                MessageDigest::sha512()
            } else if openssl.sha3_256 {
                MessageDigest::sha3_256()
            } else if openssl.sha3_512 {
                MessageDigest::sha3_512()
            } else {
                bail!("No hash function selected")
            };

            let sig = sign::openssl::sign(&secret_key, &data, digest)?;
            io::stdout().write_all(&sig)?;
        }
        SubCommand::Sign(Sign::InToto(in_toto)) => {
            let secret_key = InTotoEmbedded::read_from_disk(&in_toto.secret_key)
                .context("Failed to load secret key")?;
            let mut sig = sign::in_toto::sign(&secret_key, &in_toto.try_into()?)?;
            sig.push(b'\n');
            io::stdout().write_all(&sig)?;
        }
        SubCommand::Hsm(Hsm::Pgp(HsmPgp::Access(access))) => hsm::pgp::access(&access)?,
        SubCommand::Build(build) => build::run(build).await?,
        SubCommand::Req(req) => req::run(&req).await?,
        SubCommand::Completions(completions) => {
            args::gen_completions(&completions)?;
        }
    }

    Ok(())
}
