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

use std::collections::{HashMap, hash_map::Entry};

use pgp::types::Fingerprint;
use rpgpie::{certificate::Certificate, merge::CertificateInfo};

use crate::{Certs, RPGSOP};

pub(crate) struct MergeCerts {
    updates: HashMap<Fingerprint, Vec<CertificateInfo>>,
}

impl MergeCerts {
    pub(crate) fn new() -> Self {
        Self {
            updates: HashMap::new(),
        }
    }
}

impl MergeCerts {
    fn push_update(&mut self, cert: &Certificate) {
        let ci = CertificateInfo::Cert(cert.clone());
        match self.updates.entry(cert.fingerprint()) {
            Entry::Occupied(entry) => entry.into_mut().push(ci),
            Entry::Vacant(entry) => {
                entry.insert(vec![ci]);
            }
        }
    }
}

impl<'a> sop::ops::MergeCerts<'a, RPGSOP, Certs> for MergeCerts {
    fn merge_updates(
        mut self: Box<Self>,
        updates: &Certs,
    ) -> sop::Result<Box<dyn sop::ops::MergeCerts<'a, RPGSOP, Certs> + 'a>> {
        updates.certs.iter().for_each(|cert| self.push_update(cert));

        Ok(self)
    }

    fn merge(mut self: Box<Self>, certs: &Certs) -> sop::Result<Certs> {
        // `certs` is the stdin baseline set.
        // The input from `merge_updates` gets merged into this set.

        // This is the set of fingerprints/certs that we want to return to the user
        let mut out: HashMap<Fingerprint, Certificate> = HashMap::new();

        // Collect certificate information from stdin in `out`.
        //
        // Note: there may be duplicate versions of one cert on stdin.
        //  This block of code merges those, if necessary.
        for c in &certs.certs {
            match out.entry(c.fingerprint()) {
                Entry::Occupied(entry) => {
                    let o = entry.into_mut();
                    o.merge(vec![CertificateInfo::Cert(c.clone())])
                }
                Entry::Vacant(entry) => {
                    entry.insert(c.clone());
                }
            }
        }

        // Merge in additional updates (if any) from self.updates
        for (fp, orig) in out.iter_mut() {
            if let Some(update) = self.updates.remove(fp) {
                orig.merge(update)
            }
        }

        Ok(Certs {
            certs: out.values().cloned().collect(),
            source_name: None,
        })
    }
}
