use std::{
    fmt::{Debug, Display, Formatter},
    hash::Hash,
    ops::Deref,
    str::FromStr,
};

use base64::{display::Base64Display, Engine};

/// Errors relating to [`PeerPubkey`]
#[derive(thiserror::Error, Debug, PartialEq)]
pub enum PeerPubkeyError {
    /// Parsing the [`PeerPubkey`] failed.
    #[error("Unable to parse PeerPubkey from provided string")]
    ParseFailed,
}

pub(crate) const PEER_PUBKEY_PREFIX: &str = "pk";
pub(crate) const PEER_PUBKEY_B64_ENGINE: base64::engine::general_purpose::GeneralPurpose =
    base64::engine::general_purpose::URL_SAFE_NO_PAD;

use _impl::{ArcSlice, Vec};
#[cfg(feature = "stabby")]
mod _impl {
    use std::ops::Not;

    use safer_ffi::layout::ReprC;
    pub use stabby::{sync::ArcSlice, vec::Vec};

    use super::*;
    unsafe impl ReprC for PeerPubkey {
        type CLayout = <[*const (); 2] as ReprC>::CLayout;
        fn is_valid(it: &'_ Self::CLayout) -> bool {
            unsafe { <Self as stabby::IStable>::is_invalid(it as *const _ as *const u8).not() }
        }
    }
    impl From<ArcSlice<u8>> for PeerPubkey {
        fn from(vec: ArcSlice<u8>) -> Self {
            Self(vec)
        }
    }
    impl From<Vec<u8>> for PeerPubkey {
        fn from(vec: Vec<u8>) -> Self {
            Self(vec.into())
        }
    }
    impl From<&std::sync::Arc<[u8]>> for PeerPubkey {
        fn from(slice: &std::sync::Arc<[u8]>) -> Self {
            Self((&**slice).into())
        }
    }
}
#[cfg(not(feature = "stabby"))]
mod _impl {
    pub use std::vec::Vec;

    use super::*;
    pub type ArcSlice<T> = std::sync::Arc<[T]>;
    impl From<&std::sync::Arc<[u8]>> for PeerPubkey {
        fn from(slice: &std::sync::Arc<[u8]>) -> Self {
            Self(slice.clone())
        }
    }
}

/// The public key used by a peer.
///
/// Ditto identifies peers in the network by their public key, allowing the establishement of secure
/// communications between peers.
#[cfg_attr(feature = "stabby", stabby::stabby)]
#[repr(C)]
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PeerPubkey(pub(crate) ArcSlice<u8>);
impl Default for PeerPubkey {
    fn default() -> Self {
        Self::from([])
    }
}

impl PeerPubkey {
    /// Provide a truncated version of the peer pubkey, usually for logging purposes.
    ///
    /// The truncated display representation is not considered stable, and may change without
    /// notice.
    pub fn truncated(&self) -> impl Display + '_ {
        TruncatedPeerPubkey { orig: self }
    }
}

impl FromStr for PeerPubkey {
    type Err = PeerPubkeyError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let prefix_len = PEER_PUBKEY_PREFIX.len();
        if !s.starts_with(PEER_PUBKEY_PREFIX) {
            return Err(PeerPubkeyError::ParseFailed);
        }
        let s = &s[prefix_len..];
        let encoded_len = s.len();
        // TODO: copied from `base64::engine::general_purpose::GeneralPurpose`'s impl, replace when
        // the abstraction for it becomes public.
        let decoded_len = (encoded_len / 4 + (encoded_len % 4 > 0) as usize) * 3;

        #[cfg(not(feature = "stabby"))]
        let mut buffer = vec![0; decoded_len];

        #[cfg(feature = "stabby")]
        let mut buffer = {
            let mut buf = stabby::vec::Vec::with_capacity(decoded_len);
            for _ in 0..decoded_len {
                buf.push(0u8);
            }
            buf
        };

        let pk = PEER_PUBKEY_B64_ENGINE
            .decode_slice(s, buffer.as_mut())
            .map_err(|_| PeerPubkeyError::ParseFailed)?;
        buffer.truncate(pk);
        Ok(Self(buffer.into()))
    }
}
impl Debug for PeerPubkey {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Display::fmt(&self, f)
    }
}
/// Stringify the peer key in a chosen format recognizable in logs:
/// `pk{self:base64_url_safe_no_pad}`.
impl Display for PeerPubkey {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}{}",
            PEER_PUBKEY_PREFIX,
            Base64Display::new(&self.0, &PEER_PUBKEY_B64_ENGINE)
        )
    }
}

impl Deref for PeerPubkey {
    type Target = [u8];

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<const N: usize> From<[u8; N]> for PeerPubkey {
    fn from(vec: [u8; N]) -> Self {
        Self(Vec::from(vec.as_slice()).into())
    }
}
impl<const N: usize> From<&[u8; N]> for PeerPubkey {
    fn from(vec: &[u8; N]) -> Self {
        Self(Vec::from(vec.as_slice()).into())
    }
}
impl From<&[u8]> for PeerPubkey {
    fn from(vec: &[u8]) -> Self {
        Self(Vec::from(vec).into())
    }
}
impl From<&Self> for PeerPubkey {
    fn from(this: &Self) -> Self {
        this.clone()
    }
}

#[cfg(feature = "preview-pk-serde")]
mod serde_impl {
    use serde::{Deserialize, Serialize};

    use super::PeerPubkey;
    impl Serialize for PeerPubkey {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: serde::Serializer,
        {
            serializer.serialize_bytes(&self.0)
        }
    }

    impl<'de> Deserialize<'de> for PeerPubkey {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: serde::Deserializer<'de>,
        {
            let v = deserializer.deserialize_bytes(ByteArrayVisitor)?;
            Ok(PeerPubkey::from(v.as_slice()))
        }
    }

    struct ByteArrayVisitor;

    impl<'de> serde::de::Visitor<'de> for ByteArrayVisitor {
        type Value = Vec<u8>;

        fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
            formatter.write_str("byte array")
        }

        fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
        where
            E: serde::de::Error,
        {
            Ok(v.into())
        }

        fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
        where
            E: serde::de::Error,
        {
            Ok(v)
        }

        fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
        where
            A: serde::de::SeqAccess<'de>,
        {
            let mut values = Vec::<u8>::with_capacity(seq.size_hint().unwrap_or(0));

            while let Some(value) = seq.next_element()? {
                values.push(value);
            }

            Ok(values)
        }
    }

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

        #[test]
        fn check_serde_json() {
            let pk = PeerPubkey::from([1]);

            let as_json = serde_json::to_value(&pk).unwrap();
            assert_eq!(as_json, serde_json::json!([1]));

            let byte_slice_to_json = serde_json::to_value(&*pk.0).unwrap();
            assert_eq!(as_json, byte_slice_to_json);

            let round_trip = serde_json::from_value::<PeerPubkey>(as_json).unwrap();
            assert_eq!(pk, round_trip);

            let round_trip_byte_slice =
                serde_json::from_value::<PeerPubkey>(byte_slice_to_json).unwrap();
            assert_eq!(pk, round_trip_byte_slice);
        }

        #[test]
        fn check_serde_cbor() {
            // this test doesn't fix  the behaviour but demonstrates how serialization behaviours
            // differ between `PeerPubkey` and `[u8]`. The later of which serializes as
            // a `sequence` rather than as a byte array and that can cause issues with
            // deserialization in some cases.
            use std::ops::Deref;

            let pk = PeerPubkey::from([1]);

            let as_cbor = serde_cbor::to_vec(&pk).unwrap();
            let byte_slice_to_cbor = serde_cbor::to_vec(&pk.0.deref()).unwrap();

            // these types are not equal, PeerPubkey::serialize dumps out as a byte array
            assert_ne!(as_cbor, byte_slice_to_cbor);

            let round_trip = serde_cbor::from_slice::<PeerPubkey>(&as_cbor).unwrap();
            assert_eq!(pk, round_trip);

            let round_trip_byte_slice =
                serde_cbor::from_slice::<PeerPubkey>(&byte_slice_to_cbor).unwrap();
            assert_eq!(pk, round_trip_byte_slice);

            assert!(serde_cbor::from_slice::<Vec<u8>>(&as_cbor).is_err());
            serde_cbor::from_slice::<Vec<u8>>(&byte_slice_to_cbor).unwrap();
        }
    }
}

struct TruncatedPeerPubkey<'a> {
    orig: &'a PeerPubkey,
}

impl Display for TruncatedPeerPubkey<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let mut displayed = format!("{}", self.orig);
        if displayed.len() > 9 {
            displayed.replace_range(2..displayed.len() - 7, ".+")
        }
        f.write_str(&displayed)
    }
}
