use serde::{Deserialize, Serialize};

use crate::{
    ejsonpath::{IndexArg, Selector},
    encryption::json_indexer::{
        path_values::PathSegment,
        prefix_mac::{PrefixMac, UpdatePrefixMac},
    },
};

impl Selector {
    pub(crate) fn from_path(path: Vec<PathSegment>) -> Self {
        path.into_iter()
            .fold(Selector::Root, |acc, segment| match segment {
                PathSegment::Root => acc,
                PathSegment::ArrayWildcardItem => {
                    Selector::Index(Box::new(acc), IndexArg::Wildcard)
                }
                PathSegment::ArrayItem => Selector::Index(Box::new(acc), IndexArg::Item),
                PathSegment::ArrayPositionItem(n) => {
                    Selector::Index(Box::new(acc), IndexArg::Number(n))
                }
                PathSegment::ObjectItem(key) => {
                    Selector::Index(Box::new(acc), IndexArg::Field(key.to_owned()))
                }
            })
    }

    pub(crate) fn tokenize<const N: usize, M>(self, macca: &mut M) -> TokenizedSelector<N>
    where
        M: PrefixMac + UpdatePrefixMac<String>,
    {
        macca.update(self.canonicalise().to_string());
        TokenizedSelector(macca.finalize_reset())
    }
}

/// Represents a tokenized selector for a value in a JSON object.
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
#[serde(transparent)]
pub struct TokenizedSelector<const N: usize>(
    #[serde(
        serialize_with = "hex::serde::serialize",
        deserialize_with = "hex_decode_array"
    )]
    pub(crate) [u8; N],
);

impl<const N: usize> TokenizedSelector<N> {
    pub fn as_bytes(&self) -> [u8; N] {
        self.0
    }
}

fn hex_decode_array<'de, const N: usize, D>(deserializer: D) -> Result<[u8; N], D::Error>
where
    D: serde::Deserializer<'de>,
{
    let s = String::deserialize(deserializer)?;
    let mut out: [u8; N] = [0; N];
    hex::decode_to_slice(s, &mut out).map_err(serde::de::Error::custom)?;
    Ok(out)
}

#[cfg(test)]
mod test {
    use super::super::tests::TestPrefixMac;
    use crate::{assert_was_finalized_with, encryption::json_indexer::ste_vec::selector::Selector};

    #[test]
    fn test_tokenize() {
        let mut macca: TestPrefixMac = Default::default();
        let selector = Selector::parse("$.foo.bar[*]").unwrap();
        let _ = selector.tokenize::<16, _>(&mut macca);
        assert_was_finalized_with!(macca, "$['foo']['bar'][*]");

        let mut macca: TestPrefixMac = Default::default();
        let selector = Selector::parse("$.foo.bar").unwrap();
        let _ = selector.tokenize::<16, _>(&mut macca);
        assert_was_finalized_with!(macca, "$['foo']['bar']");
    }
}
