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

use std::{default::Default, io, io::BufRead};

use pgp::{composed::Message, types::Timestamp};
use rpgpie::{
    certificate::{Certificate, Checked},
    key::ComponentKeyPub,
};

use crate::{
    Certs,
    RPGSOP,
    util,
    util::{DebugWrapper, to_verification},
};

#[derive(Default)]
pub(crate) struct InlineVerify {
    _not_before: Option<std::time::SystemTime>,
    _not_after: Option<std::time::SystemTime>,
    certs: Vec<Certs>,
}

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

impl<'a> sop::ops::InlineVerify<'a, RPGSOP, Certs> for InlineVerify {
    fn not_before(
        self: Box<Self>,
        _t: std::time::SystemTime,
    ) -> Box<dyn sop::ops::InlineVerify<'a, RPGSOP, Certs> + 'a> {
        todo!()

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

    fn not_after(
        self: Box<Self>,
        _t: std::time::SystemTime,
    ) -> Box<dyn sop::ops::InlineVerify<'a, RPGSOP, Certs> + 'a> {
        todo!()

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

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

        Ok(self)
    }

    fn message<'d>(
        self: Box<Self>,
        data: &'d mut (dyn io::Read + Send + Sync),
    ) -> sop::Result<Box<dyn sop::ops::Ready<Vec<sop::ops::Verification>> + 'd>>
    where
        'a: 'd,
    {
        Ok(Box::new(InlineVerifyReady {
            inline_verify: *self,
            data,
        }))
    }
}

struct InlineVerifyReady<'a> {
    inline_verify: InlineVerify,
    data: &'a mut (dyn io::Read + Send + Sync),
}

/// Handling for non-CSF messages
fn verify_msg(
    msg: Message,
    sink: &mut (dyn io::Write + Send + Sync),
    certs: &[Certs],
) -> sop::Result<Vec<sop::ops::Verification>> {
    let cs: Vec<(Certificate, Option<String>)> = certs
        .iter()
        .flat_map(|c| {
            c.certs
                .iter()
                .map(|cert| (cert.clone(), c.source_name.clone()))
        })
        .collect();

    let mr = rpgpie::message::unpack(
        msg,
        vec![],
        vec![],
        vec![],
        &[],
        &cs.iter().map(|(c, _)| c.clone()).collect::<Vec<_>>(),
    )
    .expect("FIXME");

    // Do we have any mr with a non-empty "validated"?
    if !mr.validated.is_empty() {
        // yes: we got at least one positive validation

        // the cleartext should be the same in all message results!
        let text = &mr.cleartext;

        let mut vs = Vec::new();

        for (cert, ckp, sig) in &mr.validated {
            let source = cs.iter().find(|(c, _)| c == cert).map(|(_, s)| s);

            let v = to_verification(sig, cert, ckp, source.unwrap().as_deref());
            vs.push(v);
        }

        sink.write_all(text).expect("FIXME");

        Ok(vs)
    } else {
        Err(sop::errors::Error::NoSignature)
    }
}

impl sop::ops::Ready<Vec<sop::ops::Verification>> for InlineVerifyReady<'_> {
    fn to_writer(
        self: Box<Self>,
        sink: &mut (dyn io::Write + Send + Sync),
    ) -> sop::Result<Vec<sop::ops::Verification>> {
        let mut reader = io::BufReader::new(self.data);

        let buf = reader.fill_buf()?;
        if buf.is_empty() {
            panic!("empty input");
        }

        if buf[0] & 0x80 != 0 {
            // the input seems to be binary data - presumably an unarmored inline signed message
            let msg = Message::from_bytes(DebugWrapper(reader)).expect("read binary");

            verify_msg(msg, sink, &self.inline_verify.certs)
        } else {
            // the input seems to be ascii: could be an armored inline signed message or a CSF
            // message
            let (pgp, _) =
                pgp::composed::Any::from_armor(DebugWrapper(reader)).expect("read armored");

            match pgp {
                pgp::composed::Any::Cleartext(csf) => {
                    // Handling for CSF messages
                    let validated: Vec<(
                        Certificate,
                        ComponentKeyPub,
                        pgp::packet::Signature,
                        Option<String>,
                    )> = self
                        .inline_verify
                        .certs
                        .iter()
                        .flat_map(|certs| {
                            certs.certs.iter().flat_map(|c| {
                                let cc: Checked = c.clone().into();
                                let verifiers =
                                    cc.valid_signing_capable_component_keys_at(Timestamp::now());
                                let verified: Vec<_> = verifiers
                                    .iter()
                                    .flat_map(|v| {
                                        v.verify_csf(&csf).ok().map(|s| {
                                            (
                                                c.clone(),
                                                v.as_componentkey().clone(),
                                                s.clone(),
                                                certs.source_name.clone(),
                                            )
                                        })
                                    })
                                    .collect();
                                verified
                            })
                        })
                        .collect();

                    if !validated.is_empty() {
                        let text = csf.signed_text();
                        sink.write_all(text.as_bytes()).expect("FIXME");

                        let verifications: Vec<_> = validated
                            .iter()
                            .map(|(cert, key, sig, source)| {
                                util::to_verification(sig, cert, key, source.as_deref())
                            })
                            .collect();

                        Ok(verifications)
                    } else {
                        Err(sop::errors::Error::NoSignature)
                    }
                }
                pgp::composed::Any::Message(msg) => {
                    verify_msg(msg, sink, &self.inline_verify.certs)
                }
                _ => panic!("unexpected data type"),
            }
        }
    }
}
