//! Plugins allow an `Event` to be inspected and transformed during the write
//! paths of the server. This allows richer expression of some concepts and
//! helps to ensure that data is always in specific known states within the
//! `QueryServer`

use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntrySealed};
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent, ReviveRecycledEvent};
use crate::prelude::*;
use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc;

mod attrunique;
mod base;
mod cred_import;
mod default_values;
mod domain;
pub(crate) mod dyngroup;
mod eckeygen;
pub(crate) mod gidnumber;
pub(crate) mod hmac_name_unique;
mod keyobject;
mod memberof;
mod namehistory;
mod oauth2;
mod refint;
mod session;
mod spn;
mod valuedeny;

trait Plugin {
    fn id() -> &'static str;

    fn pre_create_transform(
        _qs: &mut QueryServerWriteTransaction,
        _cand: &mut Vec<EntryInvalidNew>,
        _ce: &CreateEvent,
    ) -> Result<(), OperationError> {
        error!(
            "plugin {} has an unimplemented pre_create_transform!",
            Self::id()
        );
        debug_assert!(false);
        Err(OperationError::InvalidState)
    }

    #[allow(dead_code)]
    fn pre_create(
        _qs: &mut QueryServerWriteTransaction,
        // List of what we will commit that is valid?
        _cand: &[EntrySealedNew],
        _ce: &CreateEvent,
    ) -> Result<(), OperationError> {
        error!("plugin {} has an unimplemented pre_create!", Self::id());
        debug_assert!(false);
        Err(OperationError::InvalidState)
    }

    fn post_create(
        _qs: &mut QueryServerWriteTransaction,
        // List of what we committed that was valid?
        _cand: &[EntrySealedCommitted],
        _ce: &CreateEvent,
    ) -> Result<(), OperationError> {
        error!("plugin {} has an unimplemented post_create!", Self::id());
        debug_assert!(false);
        Err(OperationError::InvalidState)
    }

    fn teardown_memorials(
        _qs: &mut QueryServerWriteTransaction,
        _memorial_pairs: &mut [(&EntrySealedCommitted, &mut EntryInvalidCommitted)],
        _re: &ReviveRecycledEvent,
    ) -> Result<(), OperationError> {
        error!("plugin {} has an unimplemented pre_modify!", Self::id());
        debug_assert!(false);
        Err(OperationError::InvalidState)
    }

    fn pre_modify(
        _qs: &mut QueryServerWriteTransaction,
        _pre_cand: &[Arc<EntrySealedCommitted>],
        _cand: &mut Vec<EntryInvalidCommitted>,
        _me: &ModifyEvent,
    ) -> Result<(), OperationError> {
        error!("plugin {} has an unimplemented pre_modify!", Self::id());
        debug_assert!(false);
        Err(OperationError::InvalidState)
    }

    fn post_modify(
        _qs: &mut QueryServerWriteTransaction,
        // List of what we modified that was valid?
        _pre_cand: &[Arc<EntrySealedCommitted>],
        _cand: &[EntrySealedCommitted],
        _ce: &ModifyEvent,
    ) -> Result<(), OperationError> {
        error!("plugin {} has an unimplemented post_modify!", Self::id());
        debug_assert!(false);
        Err(OperationError::InvalidState)
    }

    fn pre_batch_modify(
        _qs: &mut QueryServerWriteTransaction,
        _pre_cand: &[Arc<EntrySealedCommitted>],
        _cand: &mut Vec<EntryInvalidCommitted>,
        _me: &BatchModifyEvent,
    ) -> Result<(), OperationError> {
        error!(
            "plugin {} has an unimplemented pre_batch_modify!",
            Self::id()
        );
        debug_assert!(false);
        Err(OperationError::InvalidState)
    }

    fn post_batch_modify(
        _qs: &mut QueryServerWriteTransaction,
        // List of what we modified that was valid?
        _pre_cand: &[Arc<EntrySealedCommitted>],
        _cand: &[EntrySealedCommitted],
        _me: &BatchModifyEvent,
    ) -> Result<(), OperationError> {
        error!(
            "plugin {} has an unimplemented post_batch_modify!",
            Self::id()
        );
        debug_assert!(false);
        Err(OperationError::InvalidState)
    }

    fn build_memorials(
        _qs: &mut QueryServerWriteTransaction,
        _cand: &[Arc<EntrySealedCommitted>],
        _memorials: &mut BTreeMap<Uuid, EntryInitNew>,
        _de: &DeleteEvent,
    ) -> Result<(), OperationError> {
        error!(
            "plugin {} has an unimplemented run_build_memorials!",
            Self::id()
        );
        debug_assert!(false);
        Err(OperationError::InvalidState)
    }

    fn pre_delete(
        _qs: &mut QueryServerWriteTransaction,
        _cand: &mut Vec<EntryInvalidCommitted>,
        _de: &DeleteEvent,
    ) -> Result<(), OperationError> {
        error!("plugin {} has an unimplemented pre_delete!", Self::id());
        debug_assert!(false);
        Err(OperationError::InvalidState)
    }

    fn post_delete(
        _qs: &mut QueryServerWriteTransaction,
        // List of what we delete that was valid?
        _cand: &[EntrySealedCommitted],
        _ce: &DeleteEvent,
    ) -> Result<(), OperationError> {
        error!("plugin {} has an unimplemented post_delete!", Self::id());
        debug_assert!(false);
        Err(OperationError::InvalidState)
    }

    fn pre_repl_refresh(
        _qs: &mut QueryServerWriteTransaction,
        _cand: &[EntryRefreshNew],
    ) -> Result<(), OperationError> {
        error!(
            "plugin {} has an unimplemented pre_repl_refresh!",
            Self::id()
        );
        debug_assert!(false);
        Err(OperationError::InvalidState)
    }

    fn post_repl_refresh(
        _qs: &mut QueryServerWriteTransaction,
        _cand: &[EntrySealedCommitted],
    ) -> Result<(), OperationError> {
        error!(
            "plugin {} has an unimplemented post_repl_refresh!",
            Self::id()
        );
        debug_assert!(false);
        Err(OperationError::InvalidState)
    }

    // fn pre_repl_incremental(
    //     _qs: &mut QueryServerWriteTransaction,
    //     _cand: &mut [(EntryIncrementalCommitted, Arc<EntrySealedCommitted>)],
    // ) -> Result<(), OperationError> {
    //     error!(
    //         "plugin {} has an unimplemented pre_repl_incremental!",
    //         Self::id()
    //     );
    //     debug_assert!(false);
    //     Err(OperationError::InvalidState)
    // }

    fn post_repl_incremental_conflict(
        _qs: &mut QueryServerWriteTransaction,
        _cand: &[(EntrySealedCommitted, Arc<EntrySealedCommitted>)],
        _conflict_uuids: &mut BTreeSet<Uuid>,
    ) -> Result<(), OperationError> {
        error!(
            "plugin {} has an unimplemented post_repl_incremental_conflict!",
            Self::id()
        );
        debug_assert!(false);
        Err(OperationError::InvalidState)
    }

    fn post_repl_incremental(
        _qs: &mut QueryServerWriteTransaction,
        _pre_cand: &[Arc<EntrySealedCommitted>],
        _cand: &[EntrySealedCommitted],
        _conflict_uuids: &BTreeSet<Uuid>,
    ) -> Result<(), OperationError> {
        error!(
            "plugin {} has an unimplemented post_repl_incremental!",
            Self::id()
        );
        debug_assert!(false);
        Err(OperationError::InvalidState)
    }

    fn verify(_qs: &mut QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
        error!("plugin {} has an unimplemented verify!", Self::id());
        vec![Err(ConsistencyError::Unknown)]
    }
}

pub struct Plugins {}

macro_rules! run_verify_plugin {
    (
        $qs:ident,
        $results:expr,
        $target_plugin:ty
    ) => {{
        let mut r = <$target_plugin>::verify($qs);
        $results.append(&mut r);
    }};
}

impl Plugins {
    #[instrument(level = "debug", name = "plugins::run_pre_create_transform", skip_all)]
    pub fn run_pre_create_transform(
        qs: &mut QueryServerWriteTransaction,
        cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
        ce: &CreateEvent,
    ) -> Result<(), OperationError> {
        base::Base::pre_create_transform(qs, cand, ce)?;
        valuedeny::ValueDeny::pre_create_transform(qs, cand, ce)?;

        oauth2::OAuth2::pre_create_transform(qs, cand, ce)?;
        eckeygen::EcdhKeyGen::pre_create_transform(qs, cand, ce)?;
        keyobject::KeyObjectManagement::pre_create_transform(qs, cand, ce)?;
        cred_import::CredImport::pre_create_transform(qs, cand, ce)?;

        gidnumber::GidNumber::pre_create_transform(qs, cand, ce)?;
        domain::Domain::pre_create_transform(qs, cand, ce)?;
        spn::Spn::pre_create_transform(qs, cand, ce)?;
        default_values::DefaultValues::pre_create_transform(qs, cand, ce)?;
        namehistory::NameHistory::pre_create_transform(qs, cand, ce)?;
        hmac_name_unique::HmacNameUnique::pre_create_transform(qs, cand, ce)?;
        // Should always be last
        attrunique::AttrUnique::pre_create_transform(qs, cand, ce)
    }

    #[instrument(level = "trace", name = "plugins::run_pre_create", skip_all)]
    pub fn run_pre_create(
        _qs: &mut QueryServerWriteTransaction,
        _cand: &[Entry<EntrySealed, EntryNew>],
        _ce: &CreateEvent,
    ) -> Result<(), OperationError> {
        Ok(())
    }

    #[instrument(level = "debug", name = "plugins::run_post_create", skip_all)]
    pub fn run_post_create(
        qs: &mut QueryServerWriteTransaction,
        cand: &[Entry<EntrySealed, EntryCommitted>],
        ce: &CreateEvent,
    ) -> Result<(), OperationError> {
        refint::ReferentialIntegrity::post_create(qs, cand, ce)?;
        memberof::MemberOf::post_create(qs, cand, ce)
    }

    #[instrument(level = "debug", name = "plugins::run_teardown_memorials", skip_all)]
    pub fn run_teardown_memorials(
        qs: &mut QueryServerWriteTransaction,
        memorial_pairs: &mut [(&EntrySealedCommitted, &mut EntryInvalidCommitted)],
        re: &ReviveRecycledEvent,
    ) -> Result<(), OperationError> {
        hmac_name_unique::HmacNameUnique::teardown_memorials(qs, memorial_pairs, re)
    }

    #[instrument(level = "debug", name = "plugins::run_pre_modify", skip_all)]
    pub fn run_pre_modify(
        qs: &mut QueryServerWriteTransaction,
        pre_cand: &[Arc<EntrySealedCommitted>],
        cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
        me: &ModifyEvent,
    ) -> Result<(), OperationError> {
        base::Base::pre_modify(qs, pre_cand, cand, me)?;
        valuedeny::ValueDeny::pre_modify(qs, pre_cand, cand, me)?;

        oauth2::OAuth2::pre_modify(qs, pre_cand, cand, me)?;
        eckeygen::EcdhKeyGen::pre_modify(qs, pre_cand, cand, me)?;
        keyobject::KeyObjectManagement::pre_modify(qs, pre_cand, cand, me)?;
        cred_import::CredImport::pre_modify(qs, pre_cand, cand, me)?;

        gidnumber::GidNumber::pre_modify(qs, pre_cand, cand, me)?;
        domain::Domain::pre_modify(qs, pre_cand, cand, me)?;
        spn::Spn::pre_modify(qs, pre_cand, cand, me)?;
        session::SessionConsistency::pre_modify(qs, pre_cand, cand, me)?;
        default_values::DefaultValues::pre_modify(qs, pre_cand, cand, me)?;
        namehistory::NameHistory::pre_modify(qs, pre_cand, cand, me)?;
        hmac_name_unique::HmacNameUnique::pre_modify(qs, pre_cand, cand, me)?;
        // attr unique should always be last
        attrunique::AttrUnique::pre_modify(qs, pre_cand, cand, me)
    }

    #[instrument(level = "debug", name = "plugins::run_post_modify", skip_all)]
    pub fn run_post_modify(
        qs: &mut QueryServerWriteTransaction,
        pre_cand: &[Arc<Entry<EntrySealed, EntryCommitted>>],
        cand: &[Entry<EntrySealed, EntryCommitted>],
        me: &ModifyEvent,
    ) -> Result<(), OperationError> {
        refint::ReferentialIntegrity::post_modify(qs, pre_cand, cand, me)?;
        spn::Spn::post_modify(qs, pre_cand, cand, me)?;
        memberof::MemberOf::post_modify(qs, pre_cand, cand, me)
    }

    #[instrument(level = "debug", name = "plugins::run_pre_batch_modify", skip_all)]
    pub fn run_pre_batch_modify(
        qs: &mut QueryServerWriteTransaction,
        pre_cand: &[Arc<EntrySealedCommitted>],
        cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
        me: &BatchModifyEvent,
    ) -> Result<(), OperationError> {
        base::Base::pre_batch_modify(qs, pre_cand, cand, me)?;
        valuedeny::ValueDeny::pre_batch_modify(qs, pre_cand, cand, me)?;

        oauth2::OAuth2::pre_batch_modify(qs, pre_cand, cand, me)?;
        eckeygen::EcdhKeyGen::pre_batch_modify(qs, pre_cand, cand, me)?;
        keyobject::KeyObjectManagement::pre_batch_modify(qs, pre_cand, cand, me)?;
        cred_import::CredImport::pre_batch_modify(qs, pre_cand, cand, me)?;

        gidnumber::GidNumber::pre_batch_modify(qs, pre_cand, cand, me)?;
        domain::Domain::pre_batch_modify(qs, pre_cand, cand, me)?;
        spn::Spn::pre_batch_modify(qs, pre_cand, cand, me)?;
        session::SessionConsistency::pre_batch_modify(qs, pre_cand, cand, me)?;
        default_values::DefaultValues::pre_batch_modify(qs, pre_cand, cand, me)?;
        namehistory::NameHistory::pre_batch_modify(qs, pre_cand, cand, me)?;
        hmac_name_unique::HmacNameUnique::pre_batch_modify(qs, pre_cand, cand, me)?;
        // attr unique should always be last
        attrunique::AttrUnique::pre_batch_modify(qs, pre_cand, cand, me)
    }

    #[instrument(level = "debug", name = "plugins::run_post_batch_modify", skip_all)]
    pub fn run_post_batch_modify(
        qs: &mut QueryServerWriteTransaction,
        pre_cand: &[Arc<Entry<EntrySealed, EntryCommitted>>],
        cand: &[Entry<EntrySealed, EntryCommitted>],
        me: &BatchModifyEvent,
    ) -> Result<(), OperationError> {
        refint::ReferentialIntegrity::post_batch_modify(qs, pre_cand, cand, me)?;
        spn::Spn::post_batch_modify(qs, pre_cand, cand, me)?;
        memberof::MemberOf::post_batch_modify(qs, pre_cand, cand, me)
    }

    #[instrument(level = "debug", name = "plugins::run_build_memorials", skip_all)]
    pub fn run_build_memorials(
        qs: &mut QueryServerWriteTransaction,
        cand: &[Arc<EntrySealedCommitted>],
        memorials: &mut BTreeMap<Uuid, EntryInitNew>,
        de: &DeleteEvent,
    ) -> Result<(), OperationError> {
        hmac_name_unique::HmacNameUnique::build_memorials(qs, cand, memorials, de)
    }

    #[instrument(level = "debug", name = "plugins::run_pre_delete", skip_all)]
    pub fn run_pre_delete(
        qs: &mut QueryServerWriteTransaction,
        cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
        de: &DeleteEvent,
    ) -> Result<(), OperationError> {
        memberof::MemberOf::pre_delete(qs, cand, de)
    }

    #[instrument(level = "debug", name = "plugins::run_post_delete", skip_all)]
    pub fn run_post_delete(
        qs: &mut QueryServerWriteTransaction,
        cand: &[Entry<EntrySealed, EntryCommitted>],
        de: &DeleteEvent,
    ) -> Result<(), OperationError> {
        refint::ReferentialIntegrity::post_delete(qs, cand, de)?;
        memberof::MemberOf::post_delete(qs, cand, de)
    }

    #[instrument(level = "debug", name = "plugins::run_pre_repl_refresh", skip_all)]
    pub fn run_pre_repl_refresh(
        qs: &mut QueryServerWriteTransaction,
        cand: &[EntryRefreshNew],
    ) -> Result<(), OperationError> {
        attrunique::AttrUnique::pre_repl_refresh(qs, cand)
    }

    #[instrument(level = "debug", name = "plugins::run_post_repl_refresh", skip_all)]
    pub fn run_post_repl_refresh(
        qs: &mut QueryServerWriteTransaction,
        cand: &[EntrySealedCommitted],
    ) -> Result<(), OperationError> {
        refint::ReferentialIntegrity::post_repl_refresh(qs, cand)?;
        memberof::MemberOf::post_repl_refresh(qs, cand)
    }

    #[instrument(level = "debug", name = "plugins::run_pre_repl_incremental", skip_all)]
    pub fn run_pre_repl_incremental(
        _qs: &mut QueryServerWriteTransaction,
        _cand: &mut [(EntryIncrementalCommitted, Arc<EntrySealedCommitted>)],
    ) -> Result<(), OperationError> {
        // Cleanup sessions on incoming replication? May not actually
        // be needed since each node will be session checking and replicating
        // those cleanups as needed.
        // session::SessionConsistency::pre_repl_incremental(qs, cand)?;
        Ok(())
    }

    #[instrument(
        level = "debug",
        name = "plugins::run_post_repl_incremental_conflict",
        skip_all
    )]
    pub fn run_post_repl_incremental_conflict(
        qs: &mut QueryServerWriteTransaction,
        cand: &[(EntrySealedCommitted, Arc<EntrySealedCommitted>)],
        conflict_uuids: &mut BTreeSet<Uuid>,
    ) -> Result<(), OperationError> {
        // Attr unique MUST BE FIRST.
        attrunique::AttrUnique::post_repl_incremental_conflict(qs, cand, conflict_uuids)?;
        // refint probably needs to be last to ensure all refers are correctly cleaned.
        refint::ReferentialIntegrity::post_repl_incremental_conflict(qs, cand, conflict_uuids)
    }

    #[instrument(level = "debug", name = "plugins::run_post_repl_incremental", skip_all)]
    pub fn run_post_repl_incremental(
        qs: &mut QueryServerWriteTransaction,
        pre_cand: &[Arc<EntrySealedCommitted>],
        cand: &[EntrySealedCommitted],
        conflict_uuids: &BTreeSet<Uuid>,
    ) -> Result<(), OperationError> {
        // Nothing to do yet.
        // domain::Domain::post_repl_incremental(qs, pre_cand, cand, conflict_uuids)?;
        spn::Spn::post_repl_incremental(qs, pre_cand, cand, conflict_uuids)?;
        // refint MUST proceed memberof.
        refint::ReferentialIntegrity::post_repl_incremental(qs, pre_cand, cand, conflict_uuids)?;
        // Memberof MUST BE LAST.
        memberof::MemberOf::post_repl_incremental(qs, pre_cand, cand, conflict_uuids)
    }

    #[instrument(level = "debug", name = "plugins::run_verify", skip_all)]
    pub fn run_verify(
        qs: &mut QueryServerReadTransaction,
        results: &mut Vec<Result<(), ConsistencyError>>,
    ) {
        run_verify_plugin!(qs, results, base::Base);
        run_verify_plugin!(qs, results, valuedeny::ValueDeny);
        run_verify_plugin!(qs, results, attrunique::AttrUnique);
        run_verify_plugin!(qs, results, refint::ReferentialIntegrity);
        run_verify_plugin!(qs, results, keyobject::KeyObjectManagement);
        run_verify_plugin!(qs, results, dyngroup::DynGroup);
        run_verify_plugin!(qs, results, memberof::MemberOf);
        run_verify_plugin!(qs, results, spn::Spn);
    }
}
