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

use std::{io, io::BufReader, time::SystemTime};

use pgp::{
    composed::{Message, SignedSecretKey},
    crypto::sym::SymmetricKeyAlgorithm,
};
use rpgpie::{certificate::Certificate, tsk::Tsk};

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

#[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 (msg, _) =
            Message::from_reader(DebugWrapper(BufReader::new(self.ciphertext))).expect("FIXME");

        // FIXME: use provided session keys, if any

        let key_passwords: Vec<pgp::types::Password> = self
            .decrypt
            .key_passwords
            .iter()
            .map(sop::plumbing::PasswordsAreHumanReadable::normalized)
            .map(Into::into)
            .collect();

        let pkesk_decryptors: Vec<&SignedSecretKey> = self
            .decrypt
            .decryption_keys
            .iter()
            .map(|tsk| tsk.as_signed_secret_key())
            .collect();

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

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

        let sk: Vec<(SymmetricKeyAlgorithm, &[u8])> = self
            .decrypt
            .session_keys
            .iter()
            .map(|s| (s.algorithm().into(), s.key()))
            .collect();

        let mr = match rpgpie::message::unpack(
            msg,
            pkesk_decryptors,
            key_passwords.iter().collect(),
            skesk_passwords.iter().collect(),
            sk.as_slice(),
            &c,
        ) {
            Ok(mr) => mr,
            Err(e) => {
                // FIXME: probably the password(s) were wrong, but this is a bit of a guess
                //
                // FIXME: SKESK decryption failure should give a different error?

                eprintln!("{e:?}");

                return Err(sop::errors::Error::KeyIsProtected);
            }
        };

        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);

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

        Ok((session_key, verifications))
    }
}
