//! Core types for Synor contracts.

use alloc::string::String;
use borsh::{BorshDeserialize, BorshSerialize};

/// 32-byte hash value.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize)]
pub struct Hash256(pub [u8; 32]);

impl Hash256 {
    /// Creates a new hash from bytes.
    pub const fn new(bytes: [u8; 32]) -> Self {
        Hash256(bytes)
    }

    /// Creates a zero hash.
    pub const fn zero() -> Self {
        Hash256([0u8; 32])
    }

    /// Returns the raw bytes.
    pub const fn as_bytes(&self) -> &[u8; 32] {
        &self.0
    }

    /// Converts to hex string.
    pub fn to_hex(&self) -> String {
        hex::encode(self.0)
    }

    /// Creates from hex string.
    pub fn from_hex(s: &str) -> Option<Self> {
        let bytes = hex::decode(s).ok()?;
        if bytes.len() != 32 {
            return None;
        }
        let mut arr = [0u8; 32];
        arr.copy_from_slice(&bytes);
        Some(Hash256(arr))
    }
}

impl From<[u8; 32]> for Hash256 {
    fn from(bytes: [u8; 32]) -> Self {
        Hash256(bytes)
    }
}

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

/// Synor address.
///
/// Addresses are 34 bytes:
/// - 1 byte: network prefix
/// - 1 byte: address type
/// - 32 bytes: public key hash
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Address(pub [u8; 34]);

impl Default for Address {
    fn default() -> Self {
        Address([0u8; 34])
    }
}

impl BorshSerialize for Address {
    fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
        writer.write_all(&self.0)
    }
}

impl BorshDeserialize for Address {
    fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
        let mut bytes = [0u8; 34];
        reader.read_exact(&mut bytes)?;
        Ok(Address(bytes))
    }
}

impl Address {
    /// Creates a new address from raw bytes.
    pub const fn new(bytes: [u8; 34]) -> Self {
        Address(bytes)
    }

    /// Creates a zero/null address.
    pub const fn zero() -> Self {
        Address([0u8; 34])
    }

    /// Creates from just the public key hash (assumes mainnet, ed25519).
    pub fn from_pubkey_hash(hash: [u8; 32]) -> Self {
        let mut bytes = [0u8; 34];
        bytes[0] = 0x00; // Mainnet
        bytes[1] = 0x00; // Ed25519
        bytes[2..].copy_from_slice(&hash);
        Address(bytes)
    }

    /// Returns the network prefix.
    pub const fn network(&self) -> u8 {
        self.0[0]
    }

    /// Returns the address type.
    pub const fn addr_type(&self) -> u8 {
        self.0[1]
    }

    /// Returns the public key hash portion.
    pub fn pubkey_hash(&self) -> &[u8; 32] {
        self.0[2..].try_into().unwrap()
    }

    /// Returns the raw bytes.
    pub const fn as_bytes(&self) -> &[u8; 34] {
        &self.0
    }

    /// Converts to hex string.
    pub fn to_hex(&self) -> String {
        hex::encode(self.0)
    }

    /// Creates from hex string.
    pub fn from_hex(s: &str) -> Option<Self> {
        let bytes = hex::decode(s).ok()?;
        if bytes.len() != 34 {
            return None;
        }
        let mut arr = [0u8; 34];
        arr.copy_from_slice(&bytes);
        Some(Address(arr))
    }
}

impl From<[u8; 34]> for Address {
    fn from(bytes: [u8; 34]) -> Self {
        Address(bytes)
    }
}

impl From<[u8; 32]> for Address {
    fn from(hash: [u8; 32]) -> Self {
        Address::from_pubkey_hash(hash)
    }
}

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

/// Contract ID (hash of contract bytecode).
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize)]
pub struct ContractId(pub Hash256);

impl ContractId {
    /// Creates a new contract ID.
    pub const fn new(hash: Hash256) -> Self {
        ContractId(hash)
    }

    /// Creates from raw bytes.
    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
        ContractId(Hash256::new(bytes))
    }

    /// Returns as bytes.
    pub const fn as_bytes(&self) -> &[u8; 32] {
        self.0.as_bytes()
    }
}

impl From<Hash256> for ContractId {
    fn from(hash: Hash256) -> Self {
        ContractId(hash)
    }
}

impl From<[u8; 32]> for ContractId {
    fn from(bytes: [u8; 32]) -> Self {
        ContractId::from_bytes(bytes)
    }
}

/// Token amount in sompi (smallest unit, 1 SYN = 10^8 sompi).
pub type Sompi = u64;

/// Token amount constants.
pub mod amount {
    use super::Sompi;

    /// One SYN in sompi.
    pub const SYN: Sompi = 100_000_000;

    /// One milli-SYN in sompi.
    pub const MILLI_SYN: Sompi = 100_000;

    /// One micro-SYN in sompi.
    pub const MICRO_SYN: Sompi = 100;

    /// Converts SYN to sompi.
    pub const fn syn_to_sompi(syn: u64) -> Sompi {
        syn * SYN
    }

    /// Converts sompi to SYN (with truncation).
    pub const fn sompi_to_syn(sompi: Sompi) -> u64 {
        sompi / SYN
    }
}

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

    #[test]
    fn test_hash256() {
        let hash = Hash256::new([0x42; 32]);
        assert_eq!(hash.as_bytes(), &[0x42; 32]);

        let hex = hash.to_hex();
        let parsed = Hash256::from_hex(&hex).unwrap();
        assert_eq!(hash, parsed);
    }

    #[test]
    fn test_address() {
        let hash = [0x11; 32];
        let addr = Address::from_pubkey_hash(hash);
        assert_eq!(addr.network(), 0x00);
        assert_eq!(addr.addr_type(), 0x00);
        assert_eq!(addr.pubkey_hash(), &hash);
    }

    #[test]
    fn test_amount_conversion() {
        use amount::*;
        assert_eq!(syn_to_sompi(1), SYN);
        assert_eq!(sompi_to_syn(SYN), 1);
        assert_eq!(sompi_to_syn(SYN + MILLI_SYN), 1); // Truncates
    }
}
