use std::fmt::Debug;

#[cfg(feature = "cipher")]
use blowfish_rs::Blowfish;
#[cfg(feature = "cipher")]
use const_decoder::Decoder;

use crate::error::ArmourError;
use crate::zbase::ZBASE32;
use crate::{IdStr, Result};

#[inline(always)]
pub fn encode(id: &[u8; 8]) -> IdStr {
    let mut buf = IdStr::zero_filled();
    ZBASE32.encode_mut(id, unsafe { buf.as_bytes_mut() });
    buf
}

#[inline(always)]
pub fn decode(id: &str) -> Result<[u8; 8]> {
    let mut buf = [0u8; 8];
    ZBASE32
        .decode_mut(id.as_bytes(), &mut buf)
        .map_err(|_| ArmourError::ZBaseDecodeError)?;
    Ok(buf)
}

#[cfg(feature = "cipher")]
#[derive(Debug)]
pub struct Cipher(Blowfish);

#[cfg(not(feature = "cipher"))]
#[derive(Debug)]
pub struct Cipher;

#[cfg(feature = "cipher")]
impl Cipher {
    /// key should be Base64Url decoded 56 bytes long (~ 75 chars)
    pub const fn new(key: &str) -> Self {
        let bytes: [u8; 56] = Decoder::Base64Url.decode(key.as_bytes());
        Self(Blowfish::new(&bytes))
    }

    pub const fn raw(blowfish: Blowfish) -> Self {
        Self(blowfish)
    }
}

#[cfg(not(feature = "cipher"))]
impl Cipher {
    pub const fn new(_key: &str) -> Self {
        Self
    }
}

/// key should be Base64Url decoded 56 bytes long (~ 75 chars)
pub trait IdHasher: Copy + Ord + Eq + Sized + Send + Sync + Debug {
    const HASHER: Cipher;

    #[inline]
    fn encrypt(id: u64) -> u64 {
        #[cfg(feature = "cipher")]
        {
            Self::HASHER.0.encrypt_u64(id)
        }
        #[cfg(not(feature = "cipher"))]
        {
            id
        }
    }

    #[inline]
    fn decrypt(id: u64) -> u64 {
        #[cfg(feature = "cipher")]
        {
            Self::HASHER.0.decrypt_u64(id)
        }
        #[cfg(not(feature = "cipher"))]
        {
            id
        }
    }

    #[cfg(feature = "std")]
    #[inline]
    fn ser(id: u64) -> IdStr {
        #[cfg(feature = "cipher")]
        let id = Self::HASHER.0.encrypt_u64(id);
        encode(&id.to_le_bytes())
    }

    #[cfg(feature = "std")]
    #[inline]
    fn deser(id: &str) -> Result<u64> {
        if id.len() != 13 {
            return Err(ArmourError::ZBaseDecodeError);
        }
        let buf = decode(id)?;
        let de = u64::from_le_bytes(buf);
        #[cfg(feature = "cipher")]
        let de = Self::HASHER.0.decrypt_u64(de);
        Ok(de)
    }
}
