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

use std::default::Default;
use std::io;
use std::io::BufRead;

use pgp::{Deserializable, Message};
use rpgpie::certificate::Certificate;
use rpgpie::certificate::Checked;
use rpgpie::ComponentKeyPub;

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

#[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),
}

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

        let msg: Message = if buf[0] & 0x80 != 0 {
            // the input seems to be binary: presumably an unarmored inline signed message
            Message::from_bytes(reader).expect("read binary")
        } else {
            // the input seems to be ascii: could be an armored inline signed message or a CSF message
            let (pgp, _) = pgp::Any::from_armor(reader).expect("read armored");

            match pgp {
                pgp::Any::Message(msg) => msg,

                pgp::Any::Cleartext(csf) => {
                    // Handling for CSF messages
                    let validated: Vec<(
                        Certificate,
                        ComponentKeyPub,
                        pgp::Signature,
                        Option<String>,
                    )> = self
                        .inline_verify
                        .certs
                        .iter()
                        .flat_map(|certs| {
                            certs.certs.iter().flat_map(|c| {
                                let cc: Checked = c.into();
                                let verifiers = cc.valid_signing_capable_component_keys_at(
                                    &chrono::offset::Utc::now(),
                                );
                                let verified: Vec<_> = verifiers
                                    .iter()
                                    .flat_map(|v| {
                                        v.verify_csf(&csf).ok().map(|s| {
                                            (
                                                c.clone(),
                                                v.as_componentkey().clone(),
                                                s.clone().signature,
                                                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();

                        return Ok(verifications);
                    } else {
                        return Err(sop::errors::Error::NoSignature);
                    }
                }
                _ => panic!("unexpected data type"),
            }
        };

        // Handling for non-CSF messages
        let mrs: Vec<_> = self
            .inline_verify
            .certs
            .iter()
            .map(|certs| {
                let mr =
                    rpgpie::message::unpack(msg.clone(), &[], vec![], &certs.certs).expect("FIXME");
                (mr, certs.source_name.clone())
            })
            .collect();

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

            // the cleartext should be the same in all message results!
            let text = mrs[0].0.cleartext.data();

            let mut vs = Vec::new();

            for (mr, source) in &mrs {
                assert_eq!(mr.cleartext.data(), text);

                for (cert, ckp, sig) in &mr.validated {
                    let v = to_verification(sig, cert, ckp, source.as_deref());
                    vs.push(v);
                }
            }

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

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