// SPDX-FileCopyrightText: Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0

use std::collections::VecDeque;

use log::warn;
use pgp::{
    crypto::ecc_curve::ECCCurve,
    types::{KeyVersion, Password},
};
use rpgpie::tsk::Tsk;

use crate::{Keys, RPGSOP};

const PROFILE_V4_25519: &str = "draft-koch-eddsa-for-openpgp-00";
const PROFILE_RFC4880: &str = "rfc4880";
const PROFILE_RFC9580: &str = "rfc9580";

#[cfg(feature = "pqc")]
const PROFILE_RFC9980: &str = "rfc9980";
#[cfg(feature = "pqc")]
const PROFILE_RFC9980_V4: &str = "rfc9980-v4";

const PROFILES: &[(&str, &str)] = &[
    (
        "default",
        "v4 key using Curve25519 (alias: draft-koch-eddsa-for-openpgp-00)",
    ),
    #[cfg(not(feature = "pqc"))]
    ("compatibility", "v4 key using RSA (alias: rfc4880)"),
    (
        "performance",
        "v6 key using Ed25519/X25519 (alias: rfc9580)",
    ),
    #[cfg(feature = "pqc")]
    (
        "security",
        "v6 key using ML-DSA-65+Ed25519/ML-KEM-768+X25519 (alias: rfc9980)",
    ),
    #[cfg(feature = "pqc")]
    ("rfc9980-v4", "v4 key using Ed25519/ML-KEM-768+X25519"),
];

fn normalize_profile(profile: &str) -> sop::Result<&'static str> {
    match profile {
        PROFILE_V4_25519 | "default" => Ok(PROFILE_V4_25519),
        PROFILE_RFC4880 | "compatibility" => Ok(PROFILE_RFC4880),
        PROFILE_RFC9580 | "performance" => Ok(PROFILE_RFC9580),
        #[cfg(feature = "pqc")]
        PROFILE_RFC9980 | "security" => Ok(PROFILE_RFC9980),
        #[cfg(feature = "pqc")]
        PROFILE_RFC9980_V4 => Ok(PROFILE_RFC9980_V4),

        _ => Err(sop::errors::Error::UnsupportedProfile),
    }
}

pub(crate) struct GenerateKey {
    profile: &'static str,
    signing_only: bool,
    key_password: Option<sop::Password>,
    user_ids: VecDeque<String>,
}

impl GenerateKey {
    pub(crate) fn new() -> Self {
        Self {
            profile: PROFILE_V4_25519,
            signing_only: false,
            key_password: Default::default(),
            user_ids: Default::default(),
        }
    }
}

impl<'a> sop::ops::GenerateKey<'a, RPGSOP, Keys> for GenerateKey {
    fn list_profiles(&self) -> Vec<(String, String)> {
        PROFILES
            .iter()
            .map(|(p, d)| (p.to_string(), d.to_string()))
            .collect()
    }

    fn profile(
        mut self: Box<Self>,
        profile: &str,
    ) -> sop::Result<Box<dyn sop::ops::GenerateKey<'a, RPGSOP, Keys>>> {
        self.profile = normalize_profile(profile)?;
        Ok(self)
    }

    fn signing_only(mut self: Box<Self>) -> Box<dyn sop::ops::GenerateKey<'a, RPGSOP, Keys>> {
        self.signing_only = true;
        self
    }

    fn with_key_password(
        mut self: Box<Self>,
        key_password: sop::Password,
    ) -> sop::Result<Box<dyn sop::ops::GenerateKey<'a, RPGSOP, Keys>>> {
        self.key_password = Some(key_password);
        Ok(self)
    }

    fn userid(
        mut self: Box<Self>,
        user_id: &str,
    ) -> Box<dyn sop::ops::GenerateKey<'a, RPGSOP, Keys>> {
        if self.user_ids.contains(&user_id.to_string()) {
            warn!("Skipping duplicate OpenPGP User ID {user_id}");
            return self;
        }

        self.user_ids.push_back(user_id.into());
        self
    }

    fn generate(mut self: Box<Self>) -> sop::Result<Keys> {
        let primary_user_id = self.user_ids.pop_front();
        let other_user_ids = self.user_ids.into();

        let key_password: Option<&[u8]> = self
            .key_password
            .as_ref()
            .map(sop::plumbing::PasswordsAreHumanReadable::normalized);

        let key_password: Option<Password> = key_password.map(Password::from);

        let (version, key_type_pri, key_type_enc) = match self.profile {
            PROFILE_V4_25519 => (
                KeyVersion::V4,
                pgp::composed::KeyType::Ed25519Legacy,
                pgp::composed::KeyType::ECDH(ECCCurve::Curve25519Legacy),
            ),

            PROFILE_RFC4880 => (
                KeyVersion::V4,
                // RSA 4096 is compatible with Gnuk v1 (while RSA 3072 is not)
                pgp::composed::KeyType::Rsa(4096),
                pgp::composed::KeyType::Rsa(4096),
            ),

            PROFILE_RFC9580 => (
                KeyVersion::V6,
                pgp::composed::KeyType::Ed25519,
                pgp::composed::KeyType::X25519,
            ),

            #[cfg(feature = "pqc")]
            PROFILE_RFC9980 => (
                KeyVersion::V6,
                pgp::composed::KeyType::MlDsa65Ed25519,
                pgp::composed::KeyType::MlKem768X25519,
            ),

            #[cfg(feature = "pqc")]
            PROFILE_RFC9980_V4 => (
                KeyVersion::V4,
                pgp::composed::KeyType::Ed25519,
                pgp::composed::KeyType::MlKem768X25519,
            ),

            _ => return Err(sop::errors::Error::UnsupportedProfile),
        };

        let tsk = Tsk::generate(
            version,
            key_type_pri,
            if self.signing_only {
                None
            } else {
                Some(key_type_enc)
            },
            primary_user_id,
            other_user_ids,
            key_password.as_ref(),
        )
        .map_err(std::io::Error::other)?;

        Ok(Keys {
            keys: vec![tsk],
            source_name: None,
        })
    }
}
