use crate::args;
use crate::errors::*;
use openssl::ec::{EcGroup, EcKey};
use openssl::pkey::{self, PKey};
use openssl::rsa::Rsa;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum KeygenOpenssl {
    Embedded(OpensslEmbedded),
    Generate(OpensslGenerate),
}

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

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

        Ok(OpensslEmbedded {
            public_key: None,
            secret_key,
        })
    }

    pub fn to_cert(&self, binary: bool) -> Result<Vec<u8>> {
        let secret_key = PKey::private_key_from_pem(self.secret_key.as_bytes())
            .context("Failed to load signing key")?;

        if binary {
            bail!("binary encoding not implemented yet");
        } else {
            Ok(secret_key.public_key_to_pem()?)
        }
    }

    pub fn key_algo_id(&self) -> Result<&'static str> {
        let keypair = PKey::private_key_from_pem(self.secret_key.as_bytes())
            .context("Failed to load openssl key")?;
        match keypair.id() {
            pkey::Id::RSA => Ok("RSA"),
            id => bail!("Unknown key type: {:?}", id),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum KeypairType {
    Rsa,
    Secp256k1,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OpensslGenerate {
    pub keypair_type: KeypairType,
    pub bits: Option<u32>,
}

impl TryFrom<args::KeygenOpenssl> for OpensslGenerate {
    type Error = Error;

    fn try_from(openssl: args::KeygenOpenssl) -> Result<Self> {
        let keypair_type = if openssl.rsa {
            KeypairType::Rsa
        } else if openssl.secp256k1 {
            KeypairType::Secp256k1
        } else {
            bail!("No keypair type selected")
        };
        Ok(Self {
            keypair_type,
            bits: openssl.bits,
        })
    }
}

pub fn generate(config: &OpensslGenerate) -> Result<OpensslEmbedded> {
    debug!("Generating keypair...");
    let pkey = match config.keypair_type {
        KeypairType::Rsa => {
            let bits = config.bits.context("Missing bits option for rsa")?;
            let rsa = Rsa::generate(bits)?;
            PKey::from_rsa(rsa)?
        }
        KeypairType::Secp256k1 => {
            let group = EcGroup::from_curve_name(openssl::nid::Nid::SECP256K1)?;
            let secp256k1 = EcKey::generate(&group)?;
            PKey::from_ec_key(secp256k1)?
        }
    };

    let public_key = String::from_utf8(pkey.public_key_to_pem()?)?;
    let secret_key = String::from_utf8(pkey.private_key_to_pem_pkcs8()?)?;

    Ok(OpensslEmbedded {
        public_key: Some(public_key),
        secret_key,
    })
}
