use crate::hash::{decode, encode, Hash};
use anyhow::{anyhow, Result};
use serde::de::Visitor;
use serde::{de, ser, Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::io::prelude::*;
use std::io::Cursor;
use std::str::FromStr;
use thiserror::private::DisplayAsDisplay;
use unsigned_varint::encode;
use uuid::Uuid;

#[derive(Clone, PartialEq)]
pub enum Address {
    Hash(Hash),
    Uuid(Uuid),
    Attribute(String),
}

// multihash KangarooTwelve
const KANGAROO_TWELVE: u128 = 0x1d01;

// multihash identity
const IDENTITY: u128 = 0x00;

impl Address {
    pub fn encode(&self) -> Result<Vec<u8>> {
        let (hash_func_type, digest) = match self {
            Self::Hash(hash) => (KANGAROO_TWELVE, hash.0.clone()),
            Self::Uuid(uuid) => (IDENTITY, [vec![b'U'], uuid.as_bytes().to_vec()].concat()),
            Self::Attribute(attribute) => (IDENTITY, [&[b'A'], attribute.as_bytes()].concat()),
        };

        let mut result = Cursor::new(vec![0u8; 0]);
        result.write_all(encode::u128(hash_func_type, &mut encode::u128_buffer()))?;
        result.write_all(encode::usize(digest.len(), &mut encode::usize_buffer()))?;
        result.write_all(digest.as_slice())?;

        Ok(result.get_ref().clone())
    }

    pub fn decode(buffer: &[u8]) -> Result<Self> {
        let (hash_func_type, rest) = unsigned_varint::decode::u128(buffer)?;
        let (digest_len, rest) = unsigned_varint::decode::usize(rest)?;
        let digest = rest.to_vec();
        if digest_len != digest.len() {
            Err(anyhow!(format!(
                "Actual digest length ({}) does not match declared digest length ({}).",
                digest.len(),
                digest_len
            )))
        } else {
            match hash_func_type {
                KANGAROO_TWELVE => Ok(Self::Hash(Hash(digest))),
                IDENTITY => {
                    let digest_content: Vec<u8> = digest.clone().into_iter().skip(1).collect();
                    match digest[0] {
                        b'U' => Ok(Self::Uuid(uuid::Uuid::from_slice(
                            digest_content.as_slice(),
                        )?)),
                        b'A' => Ok(Self::Attribute(String::from_utf8(digest_content)?)),
                        _ => Err(anyhow!("Unknown identity marker.")),
                    }
                }
                _ => Err(anyhow!("Unknown hash function type.")),
            }
        }
    }
}

impl Serialize for Address {
    fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(encode(self.encode().map_err(ser::Error::custom)?).as_str())
    }
}

struct AddressVisitor;

impl<'de> Visitor<'de> for AddressVisitor {
    type Value = Address;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a valid UpEnd address (hash/UUID) as a multi-hashed string")
    }

    fn visit_str<E>(self, str: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        let bytes = decode(str).map_err(de::Error::custom)?;
        Address::decode(bytes.as_ref()).map_err(de::Error::custom)
    }
}

impl<'de> Deserialize<'de> for Address {
    fn deserialize<D>(deserializer: D) -> Result<Address, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_str(AddressVisitor)
    }
}

impl FromStr for Address {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Address::decode(decode(s)?.as_ref())
    }
}

impl std::fmt::Display for Address {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", encode(self.encode().map_err(|_| std::fmt::Error)?))
    }
}

impl std::fmt::Debug for Address {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.as_display())
    }
}

#[cfg(test)]
mod tests {
    use uuid::Uuid;

    use crate::addressing::Address;
    use crate::hash::Hash;

    #[test]
    fn test_hash_codec() {
        let addr = Address::Hash(Hash(vec![1, 2, 3, 4, 5]));

        let encoded = addr.encode();
        assert!(encoded.is_ok());

        let decoded = Address::decode(&encoded.unwrap());
        assert!(decoded.is_ok());

        assert_eq!(format!("{}", addr), format!("{}", decoded.unwrap()));
    }

    #[test]
    fn test_uuid_codec() {
        let addr = Address::Uuid(Uuid::new_v4());

        let encoded = addr.encode();
        assert!(encoded.is_ok());

        let decoded = Address::decode(&encoded.unwrap());
        assert!(decoded.is_ok());

        assert_eq!(format!("{}", addr), format!("{}", decoded.unwrap()));
    }

    #[test]
    fn test_attribute_codec() {
        let addr = Address::Attribute(String::from("ATTRIBUTE"));

        let encoded = addr.encode();
        assert!(encoded.is_ok());

        let decoded = Address::decode(&encoded.unwrap());
        assert!(decoded.is_ok());

        assert_eq!(format!("{}", addr), format!("{}", decoded.unwrap()));
    }
}
