use ripemd::{Digest, Ripemd160};

use crate::crypto::skein_256;
use crate::error::BrowserCoreError;
use crate::{PUBLIC_KEY_LENGTH, REQUIRED_PUBLIC_KEY_HASH_BYTE};

const SHORT_ADDRESS_HASH_BYTES: usize = 20;
const SHORT_ADDRESS_BYTES: usize = 22;

pub fn network_byte() -> u8 {
    if cfg!(feature = "mainnet") {
        0b0000_0001
    } else {
        0b0000_0010
    }
}

pub fn network_suffix() -> &'static str {
    if cfg!(feature = "mainnet") {
        "clc"
    } else {
        "cltc"
    }
}

pub fn valid_public_key(public_key: &[u8]) -> bool {
    public_key.len() == PUBLIC_KEY_LENGTH
        && public_key.first() == Some(&9)
        && skein_256(public_key)
            .first()
            .is_some_and(|byte| *byte == REQUIRED_PUBLIC_KEY_HASH_BYTE)
}

pub fn public_key_to_short_address(public_key: &[u8]) -> Result<String, BrowserCoreError> {
    if !valid_public_key(public_key) {
        return Err(BrowserCoreError::InvalidPublicKey);
    }
    let payload = Ripemd160::digest(skein_256(public_key));
    Ok(format!("{}.{}", hex::encode(payload), network_suffix()))
}

pub fn short_address_to_bytes(address: &str) -> Result<Vec<u8>, BrowserCoreError> {
    let (payload, suffix) = address
        .rsplit_once('.')
        .ok_or_else(|| BrowserCoreError::InvalidAddress(address.to_string()))?;
    if !suffix.eq_ignore_ascii_case(network_suffix()) || payload.len() != 40 {
        return Err(BrowserCoreError::InvalidAddress(address.to_string()));
    }
    let decoded =
        hex::decode(payload).map_err(|_| BrowserCoreError::InvalidAddress(address.to_string()))?;
    if decoded.len() != SHORT_ADDRESS_HASH_BYTES {
        return Err(BrowserCoreError::InvalidAddress(address.to_string()));
    }
    let mut bytes = Vec::with_capacity(SHORT_ADDRESS_BYTES);
    bytes.extend_from_slice(&decoded);
    bytes.push(b'.');
    bytes.push(network_byte());
    Ok(bytes)
}

pub fn bytes_to_short_address(bytes: &[u8]) -> Result<String, BrowserCoreError> {
    if bytes.len() != SHORT_ADDRESS_BYTES
        || bytes[SHORT_ADDRESS_HASH_BYTES] != b'.'
        || bytes[SHORT_ADDRESS_BYTES - 1] != network_byte()
    {
        return Err(BrowserCoreError::InvalidAddress(hex::encode(bytes)));
    }
    Ok(format!(
        "{}.{}",
        hex::encode(&bytes[..SHORT_ADDRESS_HASH_BYTES]),
        network_suffix()
    ))
}

pub fn vanity_address_to_bytes(address: &str) -> Result<Vec<u8>, BrowserCoreError> {
    let normalized = normalize_vanity_address(address)?;
    let (payload, _) = normalized
        .rsplit_once('.')
        .ok_or_else(|| BrowserCoreError::InvalidAddress(address.to_string()))?;
    let mut bytes = payload.as_bytes().to_vec();
    bytes.push(b'.');
    bytes.push(network_byte());
    Ok(bytes)
}

pub fn normalize_vanity_address(address: &str) -> Result<String, BrowserCoreError> {
    let (payload, suffix) = address
        .rsplit_once('.')
        .ok_or_else(|| BrowserCoreError::InvalidAddress(address.to_string()))?;
    let visible = payload.trim();
    if !suffix.eq_ignore_ascii_case(network_suffix())
        || visible.is_empty()
        || visible.len() > SHORT_ADDRESS_HASH_BYTES
        || !visible
            .chars()
            .all(|character| character.is_ascii_alphabetic())
    {
        return Err(BrowserCoreError::InvalidAddress(address.to_string()));
    }

    Ok(format!(
        "{:>width$}.{}",
        visible.to_ascii_lowercase(),
        network_suffix(),
        width = SHORT_ADDRESS_HASH_BYTES
    ))
}

#[cfg(test)]
mod tests {
    use super::{network_suffix, normalize_vanity_address, vanity_address_to_bytes};

    #[test]
    fn vanity_address_is_left_padded_and_lowercased() {
        let input = format!("BruceBates.{}", network_suffix());
        let normalized = normalize_vanity_address(&input).unwrap();

        assert_eq!(
            normalized,
            format!("          brucebates.{}", network_suffix())
        );
        assert_eq!(vanity_address_to_bytes(&input).unwrap().len(), 22);
    }

    #[test]
    fn vanity_address_rejects_non_letters() {
        let input = format!("bruce123.{}", network_suffix());

        assert!(normalize_vanity_address(&input).is_err());
    }
}
