use crate::args;
use crate::errors::*;
use sequoia_openpgp::armor;
use sequoia_openpgp::cert::prelude::*;
use sequoia_openpgp::packet::prelude::*;
use sequoia_openpgp::parse::{PacketParser, PacketParserResult, Parse};
use sequoia_openpgp::serialize::{Marshal, MarshalInto};
use sequoia_openpgp::types::KeyFlags;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum KeygenPgp {
    Embedded(PgpEmbedded),
    Generate(PgpGenerate),
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PgpEmbedded {
    pub cert: Option<String>,
    pub secret_key: String,
    pub rev: Option<String>,
}

impl PgpEmbedded {
    pub fn read_from_disk<P: AsRef<Path>>(path: P) -> Result<Self> {
        let path = path.as_ref();
        debug!("Reading pgp key from path: {:?}", path);
        let secret_key = fs::read_to_string(path)
            .with_context(|| anyhow!("Failed to read from file {:?}", path))?;
        Ok(PgpEmbedded {
            cert: None,
            secret_key,
            rev: None,
        })
    }

    pub fn to_cert(&self, binary: bool) -> Result<Vec<u8>> {
        let input = if let Some(cert) = &self.cert {
            cert
        } else {
            &self.secret_key
        };

        let cert = Cert::from_reader(input.as_bytes())?;

        let mut output = Vec::new();
        if binary {
            cert.serialize(&mut output)?;
        } else {
            cert.armored().serialize(&mut output)?;
        }

        Ok(output)
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PgpGenerate {
    pub uids: Vec<String>,
}

impl From<args::KeygenPgp> for PgpGenerate {
    fn from(pgp: args::KeygenPgp) -> Self {
        Self { uids: pgp.uids }
    }
}

pub fn debug_inspect(data: &[u8]) -> Result<()> {
    if !log::log_enabled!(log::Level::Debug) {
        return Ok(());
    }
    let mut ppr = PacketParser::from_bytes(data)?;
    while let PacketParserResult::Some(pp) = ppr {
        let (packet, next_ppr) = pp.recurse()?;
        ppr = next_ppr;
        debug!("Found packet in pgp data: {:?}", packet);
    }
    Ok(())
}

pub fn generate(config: PgpGenerate) -> Result<PgpEmbedded> {
    let mut builder = CertBuilder::new();
    for uid in &config.uids {
        debug!("Adding uid to key: {:?}", uid);
        builder = builder.add_userid(uid.as_str());
    }
    debug!("Generating keypair...");
    let (pgp, rev) = builder
        .add_signing_subkey()
        .add_transport_encryption_subkey()
        .add_storage_encryption_subkey()
        .add_subkey(
            KeyFlags::empty()
                .set_transport_encryption()
                .set_storage_encryption(),
            None,
            None,
        )
        .set_validity_period(None)
        .generate()?;

    let cert = String::from_utf8(pgp.armored().to_vec()?)?;
    let secret_key = String::from_utf8(pgp.as_tsk().armored().to_vec()?)?;

    let rev = {
        let headers = pgp.armor_headers();
        let mut headers: Vec<_> = headers
            .iter()
            .map(|value| ("Comment", value.as_str()))
            .collect();
        headers.insert(0, ("Comment", "Revocation certificate for"));

        let mut w = armor::Writer::with_headers(Vec::new(), armor::Kind::Signature, headers)?;
        Packet::Signature(rev).serialize(&mut w)?;
        String::from_utf8(w.finalize()?)?
    };

    Ok(PgpEmbedded {
        cert: Some(cert),
        secret_key,
        rev: Some(rev),
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_keygen() -> Result<()> {
        generate(PgpGenerate {
            uids: vec!["ohai".to_string()],
        })?;
        Ok(())
    }

    #[test]
    fn test_keygen_anonymous() -> Result<()> {
        generate(PgpGenerate { uids: vec![] })?;
        Ok(())
    }
}
