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

use std::io;

use pgp::types::Password;
use rpgpie::{certificate::Certificate, message, message::SignatureMode, policy::Seipd};

use crate::{Certs, Keys, RPGSOP, cmd::sign::Sign};

pub(crate) struct Encrypt {
    armor: bool,
    profile: &'static str,
    mode: sop::ops::EncryptAs,
    recipients: Vec<Certificate>,
    skesk_passwords: Vec<sop::Password>,
    sign: Sign, // Signing infrastructure, including private keys
}

impl Encrypt {
    const PROFILE_RFC4880: &'static str = "rfc4880";
    const PROFILE_RFC9580: &'static str = "rfc9580";

    const PROFILES: &'static [(&'static str, &'static str)] = &[
        (Self::PROFILE_RFC4880, "use algorithms from RFC 4880"),
        (Self::PROFILE_RFC9580, "use algorithms from RFC 9580"),
    ];

    pub(crate) fn new() -> Self {
        Self {
            armor: true,
            profile: Self::PROFILE_RFC4880,
            mode: Default::default(),
            recipients: Default::default(),
            skesk_passwords: Default::default(),
            sign: Sign::new(),
        }
    }

    fn add_cert(mut self: Box<Self>, cert: &Certificate) -> sop::Result<Box<Self>> {
        self.recipients.push(cert.clone());

        Ok(self)
    }
}

impl<'a> sop::ops::Encrypt<'a, RPGSOP, Certs, Keys> for Encrypt {
    fn no_armor(mut self: Box<Self>) -> Box<dyn sop::ops::Encrypt<'a, RPGSOP, Certs, Keys> + 'a> {
        self.armor = false;
        self
    }

    fn list_profiles(&self) -> Vec<(String, String)> {
        Self::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::Encrypt<'a, RPGSOP, Certs, Keys> + 'a>> {
        self.profile = match profile {
            Self::PROFILE_RFC4880 | "default" => Self::PROFILE_RFC4880,
            Self::PROFILE_RFC9580 => Self::PROFILE_RFC9580,
            _ => return Err(sop::errors::Error::UnsupportedProfile),
        };
        Ok(self)
    }

    fn mode(
        mut self: Box<Self>,
        mode: sop::ops::EncryptAs,
    ) -> Box<dyn sop::ops::Encrypt<'a, RPGSOP, Certs, Keys> + 'a> {
        self.sign.mode = mode.into();
        self.mode = mode;
        self
    }

    fn sign_with_keys(
        mut self: Box<Self>,
        keys: &Keys,
    ) -> sop::Result<Box<dyn sop::ops::Encrypt<'a, RPGSOP, Certs, Keys> + 'a>> {
        self.sign.add_signing_keys(keys)?;
        Ok(self)
    }

    fn with_key_password(
        mut self: Box<Self>,
        password: sop::Password,
    ) -> sop::Result<Box<dyn sop::ops::Encrypt<'a, RPGSOP, Certs, Keys> + 'a>> {
        self.sign.with_key_password.push(password);
        Ok(self)
    }

    fn with_password(
        mut self: Box<Self>,
        password: sop::Password,
    ) -> sop::Result<Box<dyn sop::ops::Encrypt<'a, RPGSOP, Certs, Keys> + 'a>> {
        self.skesk_passwords.push(password);
        Ok(self)
    }

    fn with_certs(
        mut self: Box<Self>,
        certs: &Certs,
    ) -> sop::Result<Box<dyn sop::ops::Encrypt<'a, RPGSOP, Certs, Keys> + 'a>> {
        for cert in &certs.certs {
            self = self.add_cert(cert)?;
        }
        Ok(self)
    }

    fn plaintext<'p>(
        self: Box<Self>,
        plaintext: &'p mut (dyn io::Read + Send + Sync),
    ) -> sop::Result<Box<dyn sop::ops::Ready<Option<sop::SessionKey>> + 'p>>
    where
        'a: 'p,
    {
        Ok(Box::new(EncryptReady {
            encrypt: *self,
            plaintext,
        }))
    }
}

struct EncryptReady<'a> {
    encrypt: Encrypt,
    plaintext: &'a mut (dyn io::Read + Send + Sync),
}

impl sop::ops::Ready<Option<sop::SessionKey>> for EncryptReady<'_> {
    fn to_writer(
        self: Box<Self>,
        sink: &mut (dyn io::Write + Send + Sync),
    ) -> sop::Result<Option<sop::SessionKey>> {
        if self.encrypt.recipients.is_empty() && self.encrypt.skesk_passwords.is_empty() {
            return Err(sop::errors::Error::MissingArg);
        }

        let seipd = match self.encrypt.profile {
            Encrypt::PROFILE_RFC4880 => Seipd::SEIPD1,
            Encrypt::PROFILE_RFC9580 => Seipd::SEIPD2,
            _ => unimplemented!(),
        };

        let skesk_passwords: Vec<Password> = self
            .encrypt
            .skesk_passwords
            .iter()
            .map(sop::plumbing::PasswordsAreHumanReadable::normalized)
            .map(Into::into)
            .collect();

        let signer_passwords: Vec<Password> = self
            .encrypt
            .sign
            .with_key_password
            .iter()
            .map(sop::plumbing::PasswordsAreHumanReadable::normalized)
            .map(Into::into)
            .collect();

        let (session_key, sym) = message::encrypt(
            Some(seipd),
            self.encrypt.recipients,
            skesk_passwords.iter().collect(),
            self.encrypt.sign.signers,
            &signer_passwords.iter().collect::<Vec<_>>(),
            self.plaintext,
            if self.encrypt.mode == sop::ops::EncryptAs::Binary {
                SignatureMode::Binary
            } else {
                SignatureMode::Text
            },
            sink,
            self.encrypt.armor,
        )
        .expect("FIXME");

        let session_key = sop::SessionKey::new(sym.map(u8::from).unwrap_or(0), &session_key)?;

        Ok(Some(session_key))
    }
}
