//! Addresses
//!
//! Abstract public-key addresses, and their prefixes.
//!
//! It can also parse the bech32 version of the public key if provided (used by
//! clients mostly)
use bech32::{self, FromBase32, ToBase32, Variant};
use serde::{
    de::{self, Deserialize, Deserializer},
    ser::{self, Serializer},
};
use std::{
    fmt::{self, Display},
    hash::Hash,
    ops::Deref,
};
use thiserror::Error;

use super::Id;

/// Errors
#[derive(Error, Debug)]
pub enum Error {
    /// Invalid Bech32, invalid human readable part.
    #[error("Unexpected HRP (Human readable part)")]
    UnexpectedHrp,

    /// Error decoding, provided addresses is not a hex-encoded string
    #[error("Error decoding: {0}")]
    DecodingError(#[from] hex::FromHexError),

    /// Data is longer than 32-bytes (when converted to binary/Vec<u32>)
    #[error("Data is too long ({0} bytes). Addresses are up to 32 bytes")]
    TooLong(usize),

    /// Error decoding/encoding to Bech32
    #[error("Bech32 error: {0}")]
    Bech32(#[from] bech32::Error),
}

/// Human Readable Part
///
/// Which HDR has been used to encode this address with Bech32
#[derive(Debug, Clone, Ord, PartialOrd, PartialEq, Eq, Copy)]
pub enum HumanReadablePart {
    /// Public Key / Account Address
    NPub,
    /// Private key
    NSec,
    /// A user note / posts
    Note,
}

impl Display for HumanReadablePart {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Self::NPub => "npub",
                Self::NSec => "nsec",
                Self::Note => "note",
            }
        )
    }
}

/// Public-key based Address
///
/// Clients may want to use the Bech32 encoded address *but* the protocol only
/// cares about hex-encoded binary data.
#[derive(Debug, Default, Ord, PartialOrd, Clone, Eq)]
pub struct Addr {
    /// Bytes (up to 32 bytes)
    pub bytes: Vec<u8>,
    /// HDR if provided
    pub hrp: Option<HumanReadablePart>,
}

impl From<&Id> for Addr {
    fn from(id: &Id) -> Self {
        Self {
            bytes: id.0.to_vec(),
            hrp: None,
        }
    }
}

impl From<Id> for Addr {
    fn from(id: Id) -> Self {
        Self {
            bytes: id.0.to_vec(),
            hrp: None,
        }
    }
}

impl AsRef<[u8]> for Addr {
    fn as_ref(&self) -> &[u8] {
        &self.bytes
    }
}

impl Deref for Addr {
    type Target = [u8];
    fn deref(&self) -> &[u8] {
        &self.bytes
    }
}

impl PartialEq for Addr {
    fn eq(&self, other: &Self) -> bool {
        self.bytes.eq(&other.bytes)
    }
}

impl Hash for Addr {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.bytes.hash(state)
    }
}

impl Addr {
    /// Instantiates a new Addr from a hex string with an optional human
    /// readable part
    pub fn try_from_public_key_str(
        pk: &str,
        hrp: Option<HumanReadablePart>,
    ) -> Result<Self, Error> {
        Addr::try_from_bytes(hex::decode(pk)?, hrp)
    }

    /// Converts the Addr into a vector of bytes
    pub fn take(self) -> Vec<u8> {
        self.bytes
    }

    /// Instantiates a new Addr from a vector of bytes with an optional human
    /// readable part
    pub fn try_from_bytes(bytes: Vec<u8>, hrp: Option<HumanReadablePart>) -> Result<Self, Error> {
        if bytes.len() > 32 {
            return Err(Error::TooLong(bytes.len()));
        }

        Ok(Addr { bytes, hrp })
    }

    /// Creates a new Addr with another human readable part
    pub fn change_type(self, new_hrp: Option<HumanReadablePart>) -> Self {
        Self {
            bytes: self.bytes,
            hrp: new_hrp,
        }
    }

    /// Returns the Addr to a string of hex numbers
    pub fn to_hex(&self) -> String {
        hex::encode(&self.bytes)
    }

    /// Attempts to create a human address, which is bench32 encoded
    pub fn try_to_human(&self) -> Result<String, Error> {
        Ok(bech32::encode(
            self.hrp.map(|t| t.to_string()).unwrap_or_default().as_str(),
            self.bytes.to_base32(),
            Variant::Bech32,
        )?)
    }
}

/// Converts an Addr from an string
///
/// The first approach is try decoding the the Addr as a hex-encoded string.
///
/// The second approach is decoding with bech32
impl TryFrom<&str> for Addr {
    type Error = Error;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        if let Ok(bytes) = hex::decode(value) {
            return Ok(Self { bytes, hrp: None });
        }
        let (hrp, bytes, _) = bech32::decode(value)?;
        let hrp = match hrp.to_lowercase().as_str() {
            "npub" => Ok(HumanReadablePart::NPub),
            "nsec" => Ok(HumanReadablePart::NSec),
            "note" => Ok(HumanReadablePart::Note),
            _ => Err(Error::UnexpectedHrp),
        }?;

        Ok(Self {
            bytes: Vec::<u8>::from_base32(&bytes)?,
            hrp: Some(hrp),
        })
    }
}

impl TryFrom<String> for Addr {
    type Error = Error;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        value.as_str().try_into()
    }
}

impl<'de> Deserialize<'de> for Addr {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = <String>::deserialize(deserializer)?;
        let t = s
            .try_into()
            .map_err(|e: Error| de::Error::custom(e.to_string()))?;
        Ok(t)
    }
}

impl ser::Serialize for Addr {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let str = self.to_hex();
        serializer.serialize_str(&str)
    }
}

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

    #[test]
    fn deserializes_json() {
        let json_str = "\"b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78\"";
        let x: Addr = serde_json::from_str(json_str).expect("valid pk");
        assert_eq!(None, x.hrp);
        assert_eq!(
            "npub1k2q4dqk0eqlu6tp6m5zhsh852u7a8zz9wp5ewnxxmrx2q6eu8duq3ydzzr",
            x.change_type(Some(HumanReadablePart::NPub))
                .try_to_human()
                .expect("valid addr"),
        );
    }

    #[test]
    fn deserializes_from_public_key_and_json() {
        let addr1 = Addr::try_from_public_key_str(
            "b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78",
            Some(HumanReadablePart::NPub),
        )
        .expect("valid addr");
        assert_eq!(
            "npub1k2q4dqk0eqlu6tp6m5zhsh852u7a8zz9wp5ewnxxmrx2q6eu8duq3ydzzr".to_owned(),
            addr1.try_to_human().expect("valid address")
        );

        let addr2: Addr = serde_json::from_str(
            "\"npub1k2q4dqk0eqlu6tp6m5zhsh852u7a8zz9wp5ewnxxmrx2q6eu8duq3ydzzr\"",
        )
        .expect("valid addr");
        assert_eq!(addr1, addr2);
    }
}
