use crate::zerokms::IndexKey;
use cipherstash_core::string::orderise_string;
use orderable_bytes::ToOrderableBytes;
use ore_rs::{scheme::bit2::OreAes128ChaCha20, OreCipher, OreEncrypt, OreOutput};
use zerokms_protocol::cipherstash_config::column;

use super::{
    errors::EncryptionError,
    indexer::{IndexerInit, Indexes, IndexesForQuery, QueryOp},
    orderable::pad_orderable_to_8,
    IndexTerm, Plaintext, QueryBuilder, StorageBuilder,
};

pub struct OreIndexer;

/// No options are currently supported
#[derive(Debug, Default)]
pub struct OreIndexerOptions;

impl IndexerInit for OreIndexer {
    type Args = OreIndexerOptions;
    type Error = EncryptionError;

    fn try_init<A>(_opts: A) -> Result<Self, Self::Error>
    where
        Self::Args: TryFrom<A, Error = Self::Error>,
    {
        Ok(Self)
    }
}

impl<'k> Indexes<'k, Plaintext> for OreIndexer {
    fn index(
        &self,
        mut builder: StorageBuilder<'k, Plaintext>,
    ) -> Result<StorageBuilder<'k, Plaintext>, EncryptionError> {
        let index_term = self.encrypt(builder.plaintext(), builder.index_key())?;
        builder.add_index_term(index_term);

        Ok(builder)
    }
}

impl<C> IndexesForQuery<Plaintext, C> for OreIndexer {
    fn query_index(
        &self,
        builder: QueryBuilder<Plaintext, C>,
        _op: QueryOp,
    ) -> Result<IndexTerm, EncryptionError> {
        let index_term = self.encrypt_for_query(builder.plaintext(), builder.index_key())?;
        Ok(index_term)
    }
}

impl Default for OreIndexer {
    fn default() -> Self {
        Self
    }
}

impl TryFrom<&column::IndexType> for OreIndexerOptions {
    type Error = EncryptionError;

    fn try_from(value: &column::IndexType) -> Result<Self, Self::Error> {
        match value {
            column::IndexType::Ore => Ok(Default::default()),
            _ => Err(EncryptionError::IndexingError(
                "MatchIndexerOptions can only be created from a Match index configuration"
                    .to_string(),
            )),
        }
    }
}

impl OreIndexer {
    /// Encrypts the plaintext with an appropriate ORE scheme.
    /// Strings will return an [`IndexTerm::OreArray`].
    /// All other types will return a [`IndexTerm::OreFull`].
    ///
    pub fn encrypt(
        &self,
        value: &Plaintext,
        index_key: &IndexKey,
    ) -> Result<IndexTerm, EncryptionError> {
        let mut k1: [u8; 16] = Default::default();
        let mut k2: [u8; 16] = Default::default();
        k1.copy_from_slice(&index_key.key()[0..16]);
        k2.copy_from_slice(&index_key.key()[16..]);
        let cipher: OreAes128ChaCha20 = OreCipher::init(&k1, &k2)?;

        match value {
            Plaintext::Text(Some(s)) => self.encrypt_string(s, &cipher),
            // ≤ 8-byte orderable encodings are zero-extended into a fixed
            // 8-byte plaintext block before encryption. This preserves
            // wire-format compatibility with ORE ciphertexts produced
            // before this PR (which routed every numeric `Plaintext`
            // variant through `u64` before encryption); without the
            // widening, narrower types — `i32`, `i16`, `bool`,
            // `NaiveDate` — would emit shorter `CipherText<T, N>`
            // payloads incomparable with stored data.
            Plaintext::BigInt(Some(x)) => Ok(IndexTerm::OreFull(
                pad_orderable_to_8(x.to_orderable_bytes().as_ref())
                    .encrypt(&cipher)?
                    .to_bytes(),
            )),
            Plaintext::BigUInt(Some(x)) => Ok(IndexTerm::OreFull(
                pad_orderable_to_8(x.to_orderable_bytes().as_ref())
                    .encrypt(&cipher)?
                    .to_bytes(),
            )),
            Plaintext::Boolean(Some(x)) => Ok(IndexTerm::OreFull(
                pad_orderable_to_8(x.to_orderable_bytes().as_ref())
                    .encrypt(&cipher)?
                    .to_bytes(),
            )),
            Plaintext::Float(Some(x)) => Ok(IndexTerm::OreFull(
                pad_orderable_to_8(x.to_orderable_bytes().as_ref())
                    .encrypt(&cipher)?
                    .to_bytes(),
            )),
            Plaintext::Int(Some(x)) => Ok(IndexTerm::OreFull(
                pad_orderable_to_8(x.to_orderable_bytes().as_ref())
                    .encrypt(&cipher)?
                    .to_bytes(),
            )),
            Plaintext::NaiveDate(Some(x)) => Ok(IndexTerm::OreFull(
                pad_orderable_to_8(x.to_orderable_bytes().as_ref())
                    .encrypt(&cipher)?
                    .to_bytes(),
            )),
            Plaintext::SmallInt(Some(x)) => Ok(IndexTerm::OreFull(
                pad_orderable_to_8(x.to_orderable_bytes().as_ref())
                    .encrypt(&cipher)?
                    .to_bytes(),
            )),
            // Decimal (14 bytes) and Timestamp (12 bytes) exceed the
            // 8-byte block; they have no pre-PR ORE wire format (were
            // `unimplemented!()`) and are encrypted at their native
            // ore-rs widths.
            Plaintext::Decimal(Some(x)) => Ok(IndexTerm::OreFull(x.encrypt(&cipher)?.to_bytes())),
            Plaintext::Timestamp(Some(x)) => Ok(IndexTerm::OreFull(x.encrypt(&cipher)?.to_bytes())),
            Plaintext::Text(None)
            | Plaintext::BigInt(None)
            | Plaintext::BigUInt(None)
            | Plaintext::Boolean(None)
            | Plaintext::Decimal(None)
            | Plaintext::Float(None)
            | Plaintext::Int(None)
            | Plaintext::NaiveDate(None)
            | Plaintext::SmallInt(None)
            | Plaintext::Timestamp(None) => Ok(IndexTerm::Null),
            Plaintext::Json(_) => Err(EncryptionError::IndexingError(
                "whole JSON documents are not supported by the ORE indexer".into(),
            )),
        }
    }

    pub fn encrypt_for_query(
        &self,
        value: &Plaintext,
        index_key: &IndexKey,
    ) -> Result<IndexTerm, EncryptionError> {
        // TODO: Use only the encrypt_left - currently not supported
        // by the pl/pgsql function
        let cipher = get_cipher(index_key)?;

        // See the comments in `encrypt` for why ≤ 8-byte orderable
        // encodings are widened to a fixed 8-byte plaintext block before
        // calling the cipher.
        match value {
            Plaintext::Text(Some(s)) => self.encrypt_string(s, &cipher),
            Plaintext::BigInt(Some(x)) => Ok(IndexTerm::OreFull(
                pad_orderable_to_8(x.to_orderable_bytes().as_ref())
                    .encrypt(&cipher)?
                    .to_bytes(),
            )),
            Plaintext::BigUInt(Some(x)) => Ok(IndexTerm::OreFull(
                pad_orderable_to_8(x.to_orderable_bytes().as_ref())
                    .encrypt(&cipher)?
                    .to_bytes(),
            )),
            Plaintext::Boolean(Some(x)) => Ok(IndexTerm::OreFull(
                pad_orderable_to_8(x.to_orderable_bytes().as_ref())
                    .encrypt(&cipher)?
                    .to_bytes(),
            )),
            Plaintext::Float(Some(x)) => Ok(IndexTerm::OreFull(
                pad_orderable_to_8(x.to_orderable_bytes().as_ref())
                    .encrypt(&cipher)?
                    .to_bytes(),
            )),
            Plaintext::Int(Some(x)) => Ok(IndexTerm::OreFull(
                pad_orderable_to_8(x.to_orderable_bytes().as_ref())
                    .encrypt(&cipher)?
                    .to_bytes(),
            )),
            Plaintext::NaiveDate(Some(x)) => Ok(IndexTerm::OreFull(
                pad_orderable_to_8(x.to_orderable_bytes().as_ref())
                    .encrypt(&cipher)?
                    .to_bytes(),
            )),
            Plaintext::SmallInt(Some(x)) => Ok(IndexTerm::OreFull(
                pad_orderable_to_8(x.to_orderable_bytes().as_ref())
                    .encrypt(&cipher)?
                    .to_bytes(),
            )),
            Plaintext::Decimal(Some(x)) => Ok(IndexTerm::OreFull(x.encrypt(&cipher)?.to_bytes())),
            Plaintext::Timestamp(Some(x)) => Ok(IndexTerm::OreFull(x.encrypt(&cipher)?.to_bytes())),
            Plaintext::Text(None)
            | Plaintext::BigInt(None)
            | Plaintext::BigUInt(None)
            | Plaintext::Boolean(None)
            | Plaintext::Decimal(None)
            | Plaintext::Float(None)
            | Plaintext::Int(None)
            | Plaintext::NaiveDate(None)
            | Plaintext::SmallInt(None)
            | Plaintext::Timestamp(None) => Ok(IndexTerm::Null),
            Plaintext::Json(_) => Err(EncryptionError::IndexingError(
                "whole JSON documents are not supported by the ORE indexer".into(),
            )),
        }
    }

    fn encrypt_string(
        &self,
        input_str: &str,
        cipher: &OreAes128ChaCha20,
    ) -> Result<IndexTerm, EncryptionError> {
        use ore_rs::OreEncrypt;

        let ciphertexts = orderise_string(input_str)?
            .into_iter()
            .map(|value| value.encrypt(cipher).map(|ct| ct.to_bytes()))
            .collect::<Result<Vec<Vec<u8>>, _>>()?;

        Ok(IndexTerm::OreArray(ciphertexts))
    }
}

fn get_cipher(index_key: &IndexKey) -> Result<OreAes128ChaCha20, EncryptionError> {
    let mut k1: [u8; 16] = Default::default();
    let mut k2: [u8; 16] = Default::default();
    k1.copy_from_slice(&index_key.key()[0..16]);
    k2.copy_from_slice(&index_key.key()[16..]);
    OreCipher::init(&k1, &k2).map_err(EncryptionError::from)
}

#[cfg(test)]
mod golden_vectors {
    //! ORE wire-format stability tests.
    //!
    //! ORE encryption is **non-deterministic by design** — `ore-rs`'s
    //! bit2 scheme draws a random 16-byte nonce per call and folds it
    //! into the right-side ciphertext. So we cannot assert byte-for-byte
    //! equality on the full `IndexTerm::OreFull` payload.
    //!
    //! What is stable across calls (and what in-the-wild ORE
    //! ciphertexts must remain compatible with) is the **deterministic
    //! prefix**: an 8-byte header plus the Left half of the ciphertext.
    //! For an 8-byte plaintext block under `OreAes128ChaCha20`, that's
    //! 8 + 8×16 = 136 bytes, and matching that prefix is sufficient
    //! to know that the cipher's per-block PRF output (which is what
    //! database-side `compare_raw_slices` uses to determine ordering)
    //! has not shifted.
    //!
    //! Each fixture below pins those 136 bytes against a value
    //! captured from the codebase **prior to this PR** (`origin/main`
    //! at `1045f69e1`, which used `ore-rs = 0.8.0` and a catch-all
    //! `IntoOrePlaintext::to_ore() → u64` indexer dispatch). The
    //! reference key is `[7u8; 32]`.
    //!
    //! If a test in here fails:
    //!
    //! - Length mismatch: `OreIndexer::encrypt` is now producing a
    //!   different-shaped ciphertext (e.g. 4-byte block instead of
    //!   8-byte). In-the-wild ciphertexts of that type will not be
    //!   byte-compatible at all — `compare_raw_slices` against stored
    //!   data will fail outright.
    //! - Prefix mismatch with same length: the cipher's deterministic
    //!   PRF output has shifted. In-the-wild ciphertexts may still
    //!   load but will compare incorrectly against new query terms.
    //!
    //! When intentionally rolling the wire format, regenerate the
    //! fixtures (capture harness used to produce them is referenced in
    //! the PR thread) and call out the change explicitly in the
    //! commit message.
    use super::*;
    use chrono::NaiveDate;

    /// 8-byte header + 128-byte Left half = 136 bytes (272 hex chars)
    /// for any 8-byte-plaintext encryption under `OreAes128ChaCha20`.
    const DETERMINISTIC_PREFIX_LEN: usize = 136;

    fn key() -> IndexKey {
        IndexKey::from([7u8; 32])
    }

    #[track_caller]
    fn assert_deterministic_prefix(plaintext: Plaintext, expected_prefix_hex: &str) {
        assert_eq!(
            expected_prefix_hex.len(),
            DETERMINISTIC_PREFIX_LEN * 2,
            "fixture is not the expected prefix length",
        );
        let term = OreIndexer.encrypt(&plaintext, &key()).unwrap();
        let bytes = match term {
            IndexTerm::OreFull(b) => b,
            other => panic!("expected IndexTerm::OreFull, got {other:?}"),
        };
        assert!(
            bytes.len() >= DETERMINISTIC_PREFIX_LEN,
            "ciphertext is shorter than the pre-PR deterministic prefix \
             ({} bytes, expected at least {}) — wire-format shape changed for {plaintext:?}",
            bytes.len(),
            DETERMINISTIC_PREFIX_LEN,
        );
        let actual_prefix_hex = hex::encode(&bytes[..DETERMINISTIC_PREFIX_LEN]);
        assert_eq!(
            actual_prefix_hex, expected_prefix_hex,
            "ORE deterministic prefix diverges from origin/main for {plaintext:?}",
        );
    }

    #[test]
    fn bigint_wire_format_is_stable() {
        assert_deterministic_prefix(
            Plaintext::BigInt(Some(0)),
            "d00d0d0d0d0d0d0ddaf1cba91cf962e083775b5237c06c06747140c08f50474471c10b31426c70f0\
             405fc43b6ec3a1d65b77d5f6f4accab914a2da8ebdfba7f7fd24456ae9dd3fab5b84aadb93a43b89\
             033f2a9c3a33305fafbf28974022357a1278dade28f538f20f2a218a1c145be0cc0d522b73ed052d\
             39163d5fcbdb2227cecd7cd460cdefd4",
        );
        assert_deterministic_prefix(
            Plaintext::BigInt(Some(42)),
            "d00d0d0d0d0d0d16daf1cba91cf962e083775b5237c06c06747140c08f50474471c10b31426c70f0\
             405fc43b6ec3a1d65b77d5f6f4accab914a2da8ebdfba7f7fd24456ae9dd3fab5b84aadb93a43b89\
             033f2a9c3a33305fafbf28974022357a1278dade28f538f20f2a218a1c145be0cc0d522b73ed052d\
             573c8f420a88893701ebcde6f5bb6101",
        );
        assert_deterministic_prefix(
            Plaintext::BigInt(Some(-42)),
            "d3228ef7485a3f789a88d0d95fd1c0b3f1790e02928fdd895c427411cb1288bfc18575b8ab6d8b59\
             2d6875bf185afa1b242769643510b699adc553625ad5615af0cd34ad11f5e799f4e2607809d7db52\
             c636cbe5837fd7c825642c485fdbc43014112a08408c71beb50119d58d3b5a09ea400ca8c9c8b98f\
             c8f9430da4e4f04e19edc34f050376ce",
        );
        assert_deterministic_prefix(
            Plaintext::BigInt(Some(i64::MIN)),
            "cecececececececefc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
             e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dc71281ff74c420c49\
             2853a6ef321662fb7c7a3ec3b8e36d108e069671a8e086d27f6b5fb9ca399b5f003190eab8031552\
             cf4692d29f9451a0caa6a8885a60348f",
        );
        assert_deterministic_prefix(
            Plaintext::BigInt(Some(i64::MAX)),
            "e51cd055a35629704d773701cdbd62fd05175e913aa7c349deba38250f0c82b860b9782edab63bcb\
             ba7a1325a5e3435a36a927dc6292c29f6021e3744d11be6e501b99ffcc309a397c9a0580cc234554\
             0156953eb58b4c347ef5aabd19a6116bf6c2ed449be770a7c29fe8c750f62a37806759832dcb80f0\
             2833467fb96b6a3cd2ee34cbf4e7e102",
        );
    }

    #[test]
    fn float_wire_format_is_stable() {
        assert_deterministic_prefix(
            Plaintext::Float(Some(0.0)),
            "d00d0d0d0d0d0d0ddaf1cba91cf962e083775b5237c06c06747140c08f50474471c10b31426c70f0\
             405fc43b6ec3a1d65b77d5f6f4accab914a2da8ebdfba7f7fd24456ae9dd3fab5b84aadb93a43b89\
             033f2a9c3a33305fafbf28974022357a1278dade28f538f20f2a218a1c145be0cc0d522b73ed052d\
             39163d5fcbdb2227cecd7cd460cdefd4",
        );
        assert_deterministic_prefix(
            Plaintext::Float(Some(1.5)),
            "0593dbdbdbdbdbdbbbd98421c5189a12fc5c39545de73168e3942466fd993ec48f554e421343631a\
             5d9409a5a6ea0a78d5fc05a4e43a3eaabfba978c222edd3843bb320aa9ab2e883b0e1865898a85cc\
             93c74512068e26af6ae406cd61e81c7f259c633bd621df1625312d5e0ad05672f19d79a3cfcdadf3\
             a4904732daea75ce1eca3363d5be73c6",
        );
        assert_deterministic_prefix(
            Plaintext::Float(Some(-1.5)),
            "fb03c6e95da115fbb1ca1d4a741cf59b5eb87f38e59ac17cddb8dafbe0aa6b02b134c60c41e450ff\
             016e9efbf87862fc57a5e75b586c3ed04baf962e54e865e9a3e9334e40042905462c2ea66e4e594d\
             3d894db23927e27c615fef6ddd6e1ed62a976960d641eadfbe8e5d48dfe08656c4cc3a90d7dd5a3d\
             e2422b8b5d9b078413f450fb6d113815",
        );
    }

    #[test]
    fn int_wire_format_is_stable() {
        assert_deterministic_prefix(
            Plaintext::Int(Some(0)),
            "cecececed0a9a9a9fc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
             e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dcdcb1a9700f4d32c6\
             dc41fc34a88ccf77b52c5d4e1c4f7da240c568578dbeedd5264e55589c28595a43375cfa94bac3e6\
             053991b72f39be2c921113c4a5c72a25",
        );
        assert_deterministic_prefix(
            Plaintext::Int(Some(42)),
            "cecececed0a9a933fc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
             e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dcdcb1a9700f4d32c6\
             dc41fc34a88ccf77b52c5d4e1c4f7da240c568578dbeedd5264e55589c28595a43375cfa94bac3e6\
             6ef1707cb548a0de66186e891beb57da",
        );
        assert_deterministic_prefix(
            Plaintext::Int(Some(-42)),
            "cecececed34c5286fc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
             e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dc91b9cdcc1ed9d85f\
             c879a34c46cabb5a150cf0371daad485609cd3f83ad405d4f5d21d0c9707d6f7abe3a69a6b873624\
             5005c515601cc2256cec3d1bfd2d8e68",
        );
        assert_deterministic_prefix(
            Plaintext::Int(Some(i32::MIN)),
            "cecececececececefc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
             e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dc71281ff74c420c49\
             2853a6ef321662fb7c7a3ec3b8e36d108e069671a8e086d27f6b5fb9ca399b5f003190eab8031552\
             cf4692d29f9451a0caa6a8885a60348f",
        );
        assert_deterministic_prefix(
            Plaintext::Int(Some(i32::MAX)),
            "cecececee5b127cffc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
             e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dc8628c164ef1d4d05\
             e43b4541097afcd9a73e0b1e0dcc764a61dc87a1ca5ab2b44bdd1f1e76019f8d1ac199992ebbf7b4\
             7dac70d91ece29a64730614e5941af0b",
        );
    }

    #[test]
    fn smallint_wire_format_is_stable() {
        assert_deterministic_prefix(
            Plaintext::SmallInt(Some(0)),
            "cecececececed0b3fc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
             e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dc71281ff74c420c49\
             2853a6ef321662fb7c7a3ec3b8e36d108e069671a8e086d24c9d2d0eae98361347cf0cf5512aa634\
             f8a54cd9f3cdcc80d4cb2aade70c9bf6",
        );
        assert_deterministic_prefix(
            Plaintext::SmallInt(Some(42)),
            "cecececececed0cefc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
             e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dc71281ff74c420c49\
             2853a6ef321662fb7c7a3ec3b8e36d108e069671a8e086d24c9d2d0eae98361347cf0cf5512aa634\
             5a6faec7e120919a2defe3216f7c22c0",
        );
        assert_deterministic_prefix(
            Plaintext::SmallInt(Some(-42)),
            "cecececececed304fc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
             e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dc71281ff74c420c49\
             2853a6ef321662fb7c7a3ec3b8e36d108e069671a8e086d287f297df5ffba135f4bf1663515607c7\
             10b818c379efbaceb774e1b439070ba9",
        );
    }

    #[test]
    fn boolean_wire_format_is_stable() {
        assert_deterministic_prefix(
            Plaintext::Boolean(Some(false)),
            "cecececececececefc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
             e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dc71281ff74c420c49\
             2853a6ef321662fb7c7a3ec3b8e36d108e069671a8e086d27f6b5fb9ca399b5f003190eab8031552\
             cf4692d29f9451a0caa6a8885a60348f",
        );
        assert_deterministic_prefix(
            Plaintext::Boolean(Some(true)),
            "cececececececee1fc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
             e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dc71281ff74c420c49\
             2853a6ef321662fb7c7a3ec3b8e36d108e069671a8e086d27f6b5fb9ca399b5f003190eab8031552\
             477f780ae4889b451ed2476529afeda6",
        );
    }

    #[test]
    fn naive_date_wire_format_is_stable() {
        assert_deterministic_prefix(
            Plaintext::NaiveDate(Some(NaiveDate::from_ymd_opt(2024, 1, 15).unwrap())),
            "cecececed04e899bfc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
             e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dcdcb1a9700f4d32c6\
             dc41fc34a88ccf77b3b672c5704695fe8e08cef5a45acc84f2b19a5a15496b5cb50de31976a89935\
             ef1ea9fc4e12bc8e3dd88e1a6c288f37",
        );
        assert_deterministic_prefix(
            Plaintext::NaiveDate(Some(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap())),
            "cecececed0b89efbfc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
             e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dcdcb1a9700f4d32c6\
             dc41fc34a88ccf7792dcf83723b71c2ac055b49569f690009812803e644e9ea79c82fec795ea3b5c\
             af3f364db3ac5a2a701e64007d7d5a36",
        );
    }
}
