use data_encoding::HEXLOWER;
use serde::{Deserialize, Serialize};

use crate::Result;
use crate::error::ArmourError;

// smallvec 24?
pub type KeyBytes = Vec<u8>;

#[derive(Eq, PartialEq, Clone, Copy, Serialize, Deserialize, Debug)]
#[repr(u8)]
#[serde(tag = "type", content = "data")]
pub enum KeyType {
    U8,
    U32,
    U64,
    Timestamp,
    Fuid,
    Array(usize),
}

#[derive(Eq, PartialEq, Clone, Copy, Serialize, Debug)]
#[serde(tag = "type", content = "data")]
pub enum KeyScheme {
    Typed(&'static [KeyType]),
    // Array(usize),
    Bytes,
    // Composed(&'static [KeyScheme]),
}

impl KeyScheme {
    pub const fn is_dyn_sized(&self) -> bool {
        matches!(self, KeyScheme::Bytes)
    }

    pub const fn static_size(&self) -> Option<usize> {
        match self {
            KeyScheme::Typed(types) => {
                let mut size = 0usize;
                let mut i = 0usize;

                while i < types.len() {
                    let ty = types[i];

                    match ty {
                        KeyType::U8 => size += 1,
                        KeyType::U32 => size += 4,
                        KeyType::U64 | KeyType::Timestamp | KeyType::Fuid => size += 8,
                        KeyType::Array(s) => size += s,
                    }
                    i += 1;
                }
                Some(size)
            }
            KeyScheme::Bytes => None,
        }
    }

    /// cannot be 0 (mean KeyScheme::Bytes)
    #[deprecated]
    pub const fn key_size(&self) -> usize {
        match self.static_size() {
            Some(size) => size,
            None => panic!("KeyScheme::Bytes size unknown"),
        }
    }

    /// can be 0 (mean KeyScheme::Bytes)
    #[deprecated]
    pub const fn key_size2(&self) -> usize {
        match self.static_size() {
            Some(size) => size,
            None => 0,
        }
    }

    /// - for Typed: string of values with '-' separated, e.: 1-5467-23
    /// - for Array or Bytes: hex string
    pub fn from_str(self, s: &str) -> Result<KeyBytes> {
        match self {
            KeyScheme::Typed(typ) => {
                let mut splitted = s.split('-');
                let size: usize = self.static_size().expect("cannot be None");

                let mut key = vec![0; size];

                let mut cursor = 0;

                for t in typ {
                    let s = splitted.next().ok_or(ArmourError::FromBytesError)?;

                    match t {
                        KeyType::U8 => {
                            let u = s.parse::<u8>()?;
                            let b = key.get_mut(cursor).ok_or(ArmourError::BytesLenError)?;
                            *b = u;
                            cursor += 1;
                        }
                        KeyType::U32 => {
                            let u = s.parse::<u32>()?;
                            let slice = key
                                .get_mut(cursor..cursor + 4)
                                .ok_or(ArmourError::BytesLenError)?;
                            slice.copy_from_slice(&u.to_be_bytes());

                            cursor += 4;
                        }
                        KeyType::U64 => {
                            let u = s.parse::<u64>()?;
                            let slice = key
                                .get_mut(cursor..cursor + 8)
                                .ok_or(ArmourError::BytesLenError)?;
                            slice.copy_from_slice(&u.to_be_bytes());

                            cursor += 8;
                        }
                        KeyType::Timestamp | KeyType::Fuid => {
                            let len = HEXLOWER
                                .decode_len(s.len())
                                .map_err(|_| ArmourError::HexDecodeError)?;

                            if len != 8 {
                                return Err(ArmourError::FromBytesError);
                            }

                            let slice = key
                                .get_mut(cursor..cursor + 8)
                                .ok_or(ArmourError::BytesLenError)?;

                            HEXLOWER
                                .decode_mut(s.as_bytes(), slice)
                                .map_err(|_| ArmourError::HexDecodeError)?;
                            cursor += 8;
                        }
                        KeyType::Array(size) => {
                            let len = HEXLOWER
                                .decode_len(s.len())
                                .map_err(|_| ArmourError::HexDecodeError)?;

                            if len != *size {
                                return Err(ArmourError::FromBytesError);
                            }

                            let slice = key
                                .get_mut(cursor..cursor + size)
                                .ok_or(ArmourError::BytesLenError)?;

                            HEXLOWER
                                .decode_mut(s.as_bytes(), slice)
                                .map_err(|_| ArmourError::HexDecodeError)?;
                            cursor += *size;
                        }
                    }
                }
                if splitted.next().is_some() {
                    return Err(ArmourError::FromBytesError);
                }
                Ok(key)
            }
            KeyScheme::Bytes => {
                let len = HEXLOWER
                    .decode_len(s.len())
                    .map_err(|_| ArmourError::HexDecodeError)?;

                let mut key: KeyBytes = vec![0; len];

                HEXLOWER
                    .decode_mut(s.as_bytes(), &mut key)
                    .map_err(|_| ArmourError::HexDecodeError)?;

                Ok(key)
            }
        }
    }

    /// Render key bytes back into the human-friendly string format parsed by
    /// [`from_str`]. Symmetric inverse.
    ///
    /// - `Typed`: `-`-separated parts. `U8`/`U32`/`U64` decimal; `Timestamp`/`Fuid`/`Array` hex.
    /// - `Bytes`: hex encoding of all input bytes.
    pub fn to_string_repr(self, bytes: &[u8]) -> Result<String> {
        match self {
            KeyScheme::Typed(typ) => {
                let expected = self.static_size().expect("Typed has static size");
                if bytes.len() != expected {
                    return Err(ArmourError::BytesLenError);
                }
                let mut parts: Vec<String> = Vec::with_capacity(typ.len());
                let mut cursor = 0usize;
                for t in typ {
                    match t {
                        KeyType::U8 => {
                            let b = *bytes.get(cursor).ok_or(ArmourError::BytesLenError)?;
                            parts.push(b.to_string());
                            cursor += 1;
                        }
                        KeyType::U32 => {
                            let slice: [u8; 4] = bytes
                                .get(cursor..cursor + 4)
                                .ok_or(ArmourError::BytesLenError)?
                                .try_into()
                                .map_err(|_| ArmourError::BytesLenError)?;
                            parts.push(u32::from_be_bytes(slice).to_string());
                            cursor += 4;
                        }
                        KeyType::U64 => {
                            let slice: [u8; 8] = bytes
                                .get(cursor..cursor + 8)
                                .ok_or(ArmourError::BytesLenError)?
                                .try_into()
                                .map_err(|_| ArmourError::BytesLenError)?;
                            parts.push(u64::from_be_bytes(slice).to_string());
                            cursor += 8;
                        }
                        KeyType::Timestamp | KeyType::Fuid => {
                            let slice = bytes
                                .get(cursor..cursor + 8)
                                .ok_or(ArmourError::BytesLenError)?;
                            parts.push(HEXLOWER.encode(slice));
                            cursor += 8;
                        }
                        KeyType::Array(size) => {
                            let slice = bytes
                                .get(cursor..cursor + size)
                                .ok_or(ArmourError::BytesLenError)?;
                            parts.push(HEXLOWER.encode(slice));
                            cursor += *size;
                        }
                    }
                }
                Ok(parts.join("-"))
            }
            KeyScheme::Bytes => Ok(HEXLOWER.encode(bytes)),
        }
    }
}
