use crate::errors::*;
use crate::infect;
use crate::plot::PlotExtras;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "infect", rename_all = "kebab-case")]
pub enum InfectArtifact {
    Pacman(InfectPacmanArtifact),
    Deb(InfectDebArtifact),
    Apk(InfectApkArtifact),
    Elf(InfectElfArtifact),
    ElfFwdStdin(InfectElfFwdStdinArtifact),
    Sh(InfectShArtifact),
}

impl InfectArtifact {
    pub async fn resolve(&self, plot_extras: &mut PlotExtras) -> Result<Vec<u8>> {
        match self {
            InfectArtifact::Pacman(infect) => {
                let artifact = plot_extras
                    .artifacts
                    .get(&infect.artifact)
                    .with_context(|| {
                        anyhow!(
                            "Referencing artifact that doesn't exist: {:?}",
                            infect.artifact
                        )
                    })?;

                let mut out = Vec::new();
                infect::pacman::infect(&infect.config, artifact.as_bytes(), &mut out)?;
                Ok(out)
            }
            InfectArtifact::Deb(infect) => {
                let artifact = plot_extras
                    .artifacts
                    .get(&infect.artifact)
                    .with_context(|| {
                        anyhow!(
                            "Referencing artifact that doesn't exist: {:?}",
                            infect.artifact
                        )
                    })?;

                let mut out = Vec::new();
                infect::deb::infect(&infect.config, artifact.as_bytes(), &mut out)?;
                Ok(out)
            }
            InfectArtifact::Apk(infect) => {
                let artifact = plot_extras
                    .artifacts
                    .get(&infect.artifact)
                    .with_context(|| {
                        anyhow!(
                            "Referencing artifact that doesn't exist: {:?}",
                            infect.artifact
                        )
                    })?;

                let mut out = Vec::new();
                infect::apk::infect(
                    &infect.config,
                    &plot_extras.signing_keys,
                    artifact.as_bytes(),
                    &mut out,
                )?;
                Ok(out)
            }
            InfectArtifact::Elf(infect) => {
                let artifact = plot_extras
                    .artifacts
                    .get(&infect.artifact)
                    .with_context(|| {
                        anyhow!(
                            "Referencing artifact that doesn't exist: {:?}",
                            infect.artifact
                        )
                    })?;

                let elf_artifact = if let Some(name) = &infect.elf_artifact {
                    let elf_artifact = plot_extras.artifacts.get(name).with_context(|| {
                        anyhow!(
                            "Referencing artifact that doesn't exist: {:?}",
                            infect.elf_artifact
                        )
                    })?;

                    Some(elf_artifact.as_bytes())
                } else {
                    None
                };

                let mut out = Vec::new();
                infect::elf::infect(&infect.config, artifact.as_bytes(), elf_artifact, &mut out)
                    .await?;
                Ok(out)
            }
            InfectArtifact::ElfFwdStdin(infect) => {
                let bytes = if let Some(artifact) = &infect.artifact {
                    let artifact = plot_extras.artifacts.get(artifact).with_context(|| {
                        anyhow!(
                            "Referencing artifact that doesn't exist: {:?}",
                            infect.artifact
                        )
                    })?;
                    artifact.as_bytes()
                } else if let Some(data) = &infect.data {
                    data.as_bytes()
                } else {
                    bail!(
                        "When using elf-fwd-stdin you need to provide either `artifact:` or `data:`"
                    );
                };

                let mut out = Vec::new();
                infect::elf_fwd_stdin::infect(&infect.config, bytes, &mut out).await?;
                Ok(out)
            }
            InfectArtifact::Sh(infect) => {
                let artifact = plot_extras
                    .artifacts
                    .get(&infect.artifact)
                    .with_context(|| {
                        anyhow!(
                            "Referencing artifact that doesn't exist: {:?}",
                            infect.artifact
                        )
                    })?;

                let mut out = Vec::new();
                infect::sh::infect(&infect.config, artifact.as_bytes(), &mut out).await?;
                Ok(out)
            }
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct InfectPacmanArtifact {
    pub artifact: String,
    #[serde(flatten)]
    pub config: infect::pacman::Infect,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct InfectDebArtifact {
    pub artifact: String,
    #[serde(flatten)]
    pub config: infect::deb::Infect,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct InfectApkArtifact {
    pub artifact: String,
    #[serde(flatten)]
    pub config: infect::apk::Infect,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct InfectElfArtifact {
    pub artifact: String,
    pub elf_artifact: Option<String>,
    #[serde(flatten)]
    pub config: infect::elf::Infect,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct InfectElfFwdStdinArtifact {
    pub artifact: Option<String>,
    pub data: Option<String>,
    #[serde(flatten)]
    pub config: infect::elf_fwd_stdin::Infect,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct InfectShArtifact {
    pub artifact: String,
    #[serde(flatten)]
    pub config: infect::sh::Infect,
}
