use crate::util::hash::{b58_decode, b58_encode, Hash, Hashable};
use anyhow::{anyhow, Result};
use serde::de::Visitor;
use serde::{de, ser, Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::str::FromStr;
use url::Url;
use uuid::Uuid;

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

// multihash SHA2-256
const SHA2_256: u64 = 0x12;
// multihash identity
const IDENTITY: u64 = 0x00;

type LargeMultihash = multihash::MultihashGeneric<256>;

impl Address {
    pub fn encode(&self) -> Result<Vec<u8>> {
        let hash = match self {
            Self::Hash(hash) => {
                LargeMultihash::wrap(SHA2_256, &hash.0).map_err(|err| anyhow!(err))?
            }
            Self::Uuid(uuid) => {
                LargeMultihash::wrap(IDENTITY, &[vec![b'U'], uuid.as_bytes().to_vec()].concat())
                    .map_err(|err| anyhow!(err))?
            }
            Self::Attribute(attribute) => {
                LargeMultihash::wrap(IDENTITY, &[&[b'A'], attribute.as_bytes()].concat())
                    .map_err(|err| anyhow!(err))?
            }
            Self::Url(url) => {
                LargeMultihash::wrap(IDENTITY, &[&[b'X'], url.to_string().as_bytes()].concat())
                    .map_err(|err| anyhow!(err))?
            }
        };

        Ok(hash.to_bytes())
    }

    pub fn decode(buffer: &[u8]) -> Result<Self> {
        let multihash = LargeMultihash::from_bytes(buffer)
            .map_err(|err| anyhow!("Error decoding address: {}", err))?;

        match multihash.code() {
            SHA2_256 => Ok(Self::Hash(Hash(multihash.digest().to_vec()))),
            IDENTITY => {
                let digest = multihash.digest().to_owned();
                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)?)),
                    b'X' => Ok(Self::Url(Url::parse(&String::from_utf8(digest_content)?)?)),
                    _ => Err(anyhow!("Error decoding address: Unknown identity marker.")),
                }
            }
            _ => Err(anyhow!(
                "Error decoding address: 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(b58_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 = b58_decode(str)
            .map_err(|e| de::Error::custom(format!("Error deserializing address: {}", e)))?;
        Address::decode(bytes.as_ref())
            .map_err(|e| de::Error::custom(format!("Error deserializing address: {}", e)))
    }
}

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(
            b58_decode(s)
                .map_err(|e| anyhow!("Error deserializing address: {}", e))?
                .as_ref(),
        )
    }
}

impl std::fmt::Display for Address {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            b58_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,
            "Address<{}>: {}",
            match self {
                Address::Hash(_) => "Hash",
                Address::Uuid(_) => "UUID",
                Address::Attribute(_) => "Attribute",
                Address::Url(_) => "URL",
            },
            self
        )
    }
}

pub trait Addressable: Hashable {
    fn address(&self) -> Result<Address> {
        Ok(Address::Hash(self.hash()?))
    }
}

#[cfg(test)]
mod tests {
    use anyhow::Result;
    use url::Url;
    use uuid::Uuid;

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

    #[test]
    fn test_hash_codec() -> Result<()> {
        let addr = Address::Hash(Hash(vec![1, 2, 3, 4, 5]));
        let encoded = addr.encode()?;
        let decoded = Address::decode(&encoded)?;
        assert_eq!(addr, decoded);
        Ok(())
    }

    #[test]
    fn test_uuid_codec() -> Result<()> {
        let addr = Address::Uuid(Uuid::new_v4());
        let encoded = addr.encode()?;
        let decoded = Address::decode(&encoded)?;
        assert_eq!(addr, decoded);
        Ok(())
    }

    #[test]
    fn test_attribute_codec() -> Result<()> {
        let addr = Address::Attribute(String::from("ATTRIBUTE"));
        let encoded = addr.encode()?;
        let decoded = Address::decode(&encoded)?;
        assert_eq!(addr, decoded);
        Ok(())
    }

    #[test]
    fn test_url_codec() -> Result<()> {
        let addr = Address::Url(Url::parse("https://upend.dev/an/url/that/is/particularly/long/because/multihash/used/to/have/a/small/limit").unwrap());
        let encoded = addr.encode()?;
        let decoded = Address::decode(&encoded)?;
        assert_eq!(addr, decoded);
        Ok(())
    }
}
