use serde::de::{Error as _, Unexpected};
use serde::{Deserialize, Deserializer, Serialize, Serializer};

#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SolidKey([u8; 16]);

impl SolidKey {
    #[must_use]
    pub const fn from_bytes(bytes: [u8; 16]) -> Self {
        Self(bytes)
    }

    #[must_use]
    pub const fn bytes(self) -> [u8; 16] {
        self.0
    }

    #[must_use]
    pub fn hex(self) -> String {
        self.0
            .iter()
            .fold(String::with_capacity(32), |mut acc, &byte| {
                acc.push(nibble(byte >> 4));
                acc.push(nibble(byte & 0x0f));
                acc
            })
    }

    fn parse_hex(text: &str) -> Option<Self> {
        let bytes = text.as_bytes();
        if bytes.len() != 32 {
            return None;
        }
        (0..16)
            .try_fold([0u8; 16], |mut acc, index| {
                let hi = from_nibble(bytes[index * 2])?;
                let lo = from_nibble(bytes[index * 2 + 1])?;
                acc[index] = (hi << 4) | lo;
                Some(acc)
            })
            .map(Self)
    }
}

const fn nibble(value: u8) -> char {
    match value {
        0..=9 => (b'0' + value) as char,
        _ => (b'a' + value - 10) as char,
    }
}

const fn from_nibble(byte: u8) -> Option<u8> {
    match byte {
        b'0'..=b'9' => Some(byte - b'0'),
        b'a'..=b'f' => Some(byte - b'a' + 10),
        _ => None,
    }
}

impl core::fmt::Display for SolidKey {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str(&self.hex())
    }
}

impl Serialize for SolidKey {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_str(&self.hex())
    }
}

impl<'de> Deserialize<'de> for SolidKey {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let text = String::deserialize(deserializer)?;
        Self::parse_hex(&text)
            .ok_or_else(|| D::Error::invalid_value(Unexpected::Str(&text), &"a 32-char hex digest"))
    }
}

#[cfg(test)]
mod tests {
    use super::SolidKey;

    #[test]
    fn hex_round_trips() {
        let key = SolidKey::from_bytes([
            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
            0xee, 0xff,
        ]);
        assert_eq!(key.hex(), "00112233445566778899aabbccddeeff");
        assert_eq!(SolidKey::parse_hex(&key.hex()), Some(key));
    }

    #[test]
    fn parse_rejects_wrong_length_and_non_hex() {
        assert_eq!(SolidKey::parse_hex("abcd"), None);
        assert_eq!(SolidKey::parse_hex(&"z".repeat(32)), None);
    }
}
