use super::{mac_term::MacTerm, ste_plaintext_term::StePlaintextTerm, term_builder::TermBuilder};
use crate::encryption::{
    json_indexer::{
        prefix_mac::{PrefixMac, UpdatePrefixMac},
        ste_vec::EntryWithEncryptedTerm,
    },
    EncryptionError, Plaintext, TokenizedSelector,
};

#[cfg_attr(test, derive(Debug))]
pub(crate) struct EntryWithTokenizedSelector<const N: usize> {
    pub(super) tokenized_selector: TokenizedSelector<N>,
    pub(super) term: StePlaintextTerm,
    pub(super) plaintext: Plaintext,
    pub(super) parent_is_array: bool,
}

impl<const N: usize> EntryWithTokenizedSelector<N> {
    pub(crate) fn new(
        tokenized_selector: TokenizedSelector<N>,
        term: StePlaintextTerm,
        plaintext: Plaintext,
        parent_is_array: bool,
    ) -> Self {
        Self {
            tokenized_selector,
            term,
            plaintext,
            parent_is_array,
        }
    }

    pub(in super::super) fn build_term<TB, M>(
        self,
        macca: &mut M,
    ) -> Result<EntryWithEncryptedTerm<N>, EncryptionError>
    where
        TB: TermBuilder,
        M: PrefixMac
            + UpdatePrefixMac<MacTerm>
            + UpdatePrefixMac<&'static str>
            + UpdatePrefixMac<TokenizedSelector<N>>,
    {
        // TODO: Dan 2024/10/23 The selector is only included in the generated term for MAC term types
        // ORE terms do not have the selector included in the term and therefore
        // will not be unique across different selectors
        // This simplifies the query logic for now but may need to be revisited
        let term = match self.term {
            StePlaintextTerm::Mac(t) => TB::mac_term(macca, self.tokenized_selector, t),
            StePlaintextTerm::Orderable(t) => TB::orderable_term(t, macca)?,
        };

        Ok(EntryWithEncryptedTerm {
            term,
            tokenized_selector: self.tokenized_selector,
            plaintext: self.plaintext,
            parent_is_array: self.parent_is_array,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::{
        super::{ste_plaintext_term::OrderableTerm, MacTerm, StePlaintextTerm},
        EntryWithTokenizedSelector,
    };
    use crate::{
        assert_was_finalized_with,
        encryption::{
            json_indexer::ste_vec::tests::TestPrefixMac, orderable::orderable_to_u64, Plaintext,
            TokenizedSelector,
        },
    };
    use orderable_bytes::ToOrderableBytes;

    #[test]
    fn test_build_mac_term() {
        fn build_term(
            tokenized_selector: TokenizedSelector<16>,
            term: StePlaintextTerm,
        ) -> TestPrefixMac {
            let mut macca: TestPrefixMac = Default::default();
            let _ =
                EntryWithTokenizedSelector::new(tokenized_selector, term, Plaintext::new(0), false)
                    .build_term::<super::super::CompatTermBuilder, _>(&mut macca);
            macca
        }

        assert_was_finalized_with!(
            build_term(
                TokenizedSelector([1; 16]),
                StePlaintextTerm::Mac(MacTerm::Null)
            ),
            [1; 16],
            MacTerm::INFO_NULL
        );
        assert_was_finalized_with!(
            build_term(
                TokenizedSelector([2; 16]),
                StePlaintextTerm::Mac(MacTerm::Null)
            ),
            [2; 16],
            MacTerm::INFO_NULL
        );
        assert_was_finalized_with!(
            build_term(
                TokenizedSelector([1; 16]),
                StePlaintextTerm::Mac(MacTerm::Array)
            ),
            [1; 16],
            MacTerm::INFO_ARRY
        );
        assert_was_finalized_with!(
            build_term(
                TokenizedSelector([1; 16]),
                StePlaintextTerm::Mac(MacTerm::Map)
            ),
            [1; 16],
            MacTerm::INFO_MAP
        );
        assert_was_finalized_with!(
            build_term(
                TokenizedSelector([1; 16]),
                StePlaintextTerm::Mac(MacTerm::Bool(true))
            ),
            [1; 16],
            MacTerm::INFO_TRUE
        );
        assert_was_finalized_with!(
            build_term(
                TokenizedSelector([1; 16]),
                StePlaintextTerm::Mac(MacTerm::Bool(false))
            ),
            [1; 16],
            MacTerm::INFO_FALSE
        );
    }

    /// `CompatTermBuilder` builds OPE orderable terms, so the term-key MAC is
    /// domain-separated with the `"OPE"` info string.
    #[test]
    fn test_build_compat_orderable_term_uses_ope_info() {
        fn build_term(
            tokenized_selector: TokenizedSelector<16>,
            term: StePlaintextTerm,
        ) -> TestPrefixMac {
            let mut macca: TestPrefixMac = Default::default();
            let _ =
                EntryWithTokenizedSelector::new(tokenized_selector, term, Plaintext::new(0), false)
                    .build_term::<super::super::CompatTermBuilder, _>(&mut macca);
            macca
        }

        assert_was_finalized_with!(
            build_term(
                TokenizedSelector([1; 16]),
                StePlaintextTerm::Orderable(OrderableTerm::String(String::from("hello")))
            ),
            "OPE"
        );
        assert_was_finalized_with!(
            build_term(
                TokenizedSelector([1; 16]),
                StePlaintextTerm::Orderable(OrderableTerm::Number(orderable_to_u64(
                    1i32.to_orderable_bytes().as_ref()
                )))
            ),
            "OPE"
        );
    }

    /// `StandardTermBuilder` builds ORE orderable terms, so the term-key MAC is
    /// domain-separated with the `"ORE"` info string.
    #[test]
    fn test_build_standard_orderable_term_uses_ore_info() {
        fn build_term(
            tokenized_selector: TokenizedSelector<16>,
            term: StePlaintextTerm,
        ) -> TestPrefixMac {
            let mut macca: TestPrefixMac = Default::default();
            let _ =
                EntryWithTokenizedSelector::new(tokenized_selector, term, Plaintext::new(0), false)
                    .build_term::<super::super::StandardTermBuilder, _>(&mut macca);
            macca
        }

        assert_was_finalized_with!(
            build_term(
                TokenizedSelector([1; 16]),
                StePlaintextTerm::Orderable(OrderableTerm::String(String::from("hello")))
            ),
            "ORE"
        );
        assert_was_finalized_with!(
            build_term(
                TokenizedSelector([1; 16]),
                StePlaintextTerm::Orderable(OrderableTerm::Number(orderable_to_u64(
                    1i32.to_orderable_bytes().as_ref()
                )))
            ),
            "ORE"
        );
    }
}
