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

use std::io;
use std::time::SystemTime;

use pgp::composed::{Deserializable, Message};
use rpgpie::certificate::Certificate;
use rpgpie::message::{PkeskDecryptor, SoftkeyPkeskDecryptor};
use rpgpie::tsk::Tsk;

use crate::cmd::verify::Verify;
use crate::{util, Certs, Keys, RPGSOP};

#[derive(Default)]
pub(crate) struct Decrypt {
    verify: Verify,
    session_keys: Vec<sop::SessionKey>,
    decryption_keys: Vec<Tsk>,
    key_passwords: Vec<sop::Password>, // Passwords for asymmetric component key material
    skesk_passwords: Vec<sop::Password>,
}

impl Decrypt {
    pub(crate) fn new() -> Self {
        Default::default()
    }
}

impl<'a> sop::ops::Decrypt<'a, RPGSOP, Certs, Keys> for Decrypt {
    fn verify_not_before(
        self: Box<Self>,
        _t: SystemTime,
    ) -> Box<dyn sop::ops::Decrypt<'a, RPGSOP, Certs, Keys> + 'a> {
        todo!();

        // self.verify.not_before = Some(t);
        // self
    }

    fn verify_not_after(
        self: Box<Self>,
        _t: SystemTime,
    ) -> Box<dyn sop::ops::Decrypt<'a, RPGSOP, Certs, Keys> + 'a> {
        todo!()

        // self.verify.not_after = Some(t);
        // self
    }

    fn verify_with_certs(
        mut self: Box<Self>,
        certs: &Certs,
    ) -> sop::Result<Box<dyn sop::ops::Decrypt<'a, RPGSOP, Certs, Keys> + 'a>> {
        self.verify.certs.push(certs.clone());
        Ok(self)
    }

    fn with_session_key(
        mut self: Box<Self>,
        session_key: sop::SessionKey,
    ) -> sop::Result<Box<dyn sop::ops::Decrypt<'a, RPGSOP, Certs, Keys> + 'a>> {
        self.session_keys.push(session_key);
        Ok(self)
    }

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

    fn with_keys(
        mut self: Box<Self>,
        keys: &Keys,
    ) -> sop::Result<Box<dyn sop::ops::Decrypt<'a, RPGSOP, Certs, Keys> + 'a>> {
        keys.keys
            .iter()
            .for_each(|tsk| self.decryption_keys.push(tsk.clone()));
        Ok(self)
    }

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

    fn ciphertext<'d>(
        self: Box<Self>,
        ciphertext: &'d mut (dyn io::Read + Send + Sync),
    ) -> sop::Result<
        Box<dyn sop::ops::Ready<(Option<sop::SessionKey>, Vec<sop::ops::Verification>)> + 'd>,
    >
    where
        'a: 'd,
    {
        Ok(Box::new(DecryptReady {
            decrypt: *self,
            ciphertext,
        }))
    }
}

struct DecryptReady<'a> {
    decrypt: Decrypt,
    ciphertext: &'a mut (dyn io::Read + Send + Sync),
}

impl sop::ops::Ready<(Option<sop::SessionKey>, Vec<sop::ops::Verification>)> for DecryptReady<'_> {
    fn to_writer(
        self: Box<Self>,
        sink: &mut (dyn io::Write + Send + Sync),
    ) -> sop::Result<(Option<sop::SessionKey>, Vec<sop::ops::Verification>)> {
        let (mut iter, _header) = Message::from_reader_many(self.ciphertext).expect("FIXME");

        if let Some(Ok(msg)) = iter.next() {
            // FIXME: use provided session keys, if any

            let key_passwords: Vec<_> = self
                .decrypt
                .key_passwords
                .iter()
                .map(sop::plumbing::PasswordsAreHumanReadable::normalized)
                .collect();

            let pkesk_decryptors: Vec<_> = self
                .decrypt
                .decryption_keys
                .iter()
                .map(|tsk| SoftkeyPkeskDecryptor::new(tsk.clone(), key_passwords.clone()))
                .map(|spd| Box::new(spd) as Box<dyn PkeskDecryptor>)
                .collect();

            let skesk_passwords = self
                .decrypt
                .skesk_passwords
                .iter()
                .map(sop::plumbing::PasswordsAreHumanReadable::normalized)
                .collect();

            let c: Vec<Certificate> = self
                .decrypt
                .verify
                .certs
                .into_iter()
                .flat_map(|c| c.certs)
                .collect();

            let mr = {
                let mut decrypted = None;

                for sk in self.decrypt.session_keys {
                    if let Ok(mr) = rpgpie::message::unpack_by_session_key(
                        &msg,
                        sk.key(),
                        sk.algorithm().into(),
                        &c,
                    ) {
                        decrypted = Some(mr);
                        break;
                    }
                }

                if decrypted.is_none() {
                    if let Ok(mr) = rpgpie::message::unpack(
                        msg,
                        pkesk_decryptors.as_slice(),
                        skesk_passwords,
                        &c,
                    ) {
                        decrypted = Some(mr);
                    }
                }

                let Some(mr) = decrypted else {
                    // FIXME: probably the password(s) were wrong, but this is a bit of a guess
                    //
                    // FIXME: SKESK decryption failure should give a different error?
                    return Err(sop::errors::Error::KeyIsProtected);
                };

                mr
            };

            let session_key = mr
                .session_key
                .as_ref()
                .map(|sk| sop::SessionKey::new(sk.0, &(sk.1)).expect("FIXME"));

            let verifications = util::result_to_verifications(&mr);

            assert!(
                iter.next().is_none(),
                "message must be empty at this point!"
            );

            sink.write_all(mr.cleartext.data()).expect("FIXME");

            Ok((session_key, verifications))
        } else {
            panic!("no message found");
        }
    }
}
