//! Address encoding for Synor blockchain.
//!
//! Uses Bech32m encoding with network-specific prefixes.

use sha3::{Digest, Sha3_256};
use wasm_bindgen::prelude::*;

/// Bech32m character set.
const CHARSET: &[u8; 32] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";

/// Bech32m constant for checksum.
const BECH32M_CONST: u32 = 0x2bc830a3;

/// Network prefixes.
pub fn network_prefix(network: &str) -> Result<&'static str, JsValue> {
    match network.to_lowercase().as_str() {
        "mainnet" | "main" => Ok("synor"),
        "testnet" | "test" => Ok("tsynor"),
        "devnet" | "dev" => Ok("dsynor"),
        _ => Err(JsValue::from_str(&format!("Unknown network: {}", network))),
    }
}

/// Encode a public key as a Synor address.
pub fn encode_address(network: &str, pubkey: &[u8]) -> Result<String, JsValue> {
    let prefix = network_prefix(network)?;

    // Hash the public key
    let mut hasher = Sha3_256::new();
    hasher.update(pubkey);
    let hash = hasher.finalize();

    // Take first 20 bytes (160 bits) for the address
    let data = &hash[..20];

    // Encode as bech32m
    bech32m_encode(prefix, data)
}

/// Decode a Synor address to get network and public key hash.
#[wasm_bindgen(js_name = decodeAddress)]
pub fn decode_address(address: &str) -> Result<JsValue, JsValue> {
    let (hrp, data) = bech32m_decode(address)?;

    let network = match hrp.as_str() {
        "synor" => "mainnet",
        "tsynor" => "testnet",
        "dsynor" => "devnet",
        _ => return Err(JsValue::from_str("Unknown address prefix")),
    };

    // Return as JS object
    let result = js_sys::Object::new();
    js_sys::Reflect::set(&result, &"network".into(), &network.into())?;
    js_sys::Reflect::set(&result, &"hash".into(), &hex::encode(&data).into())?;

    Ok(result.into())
}

/// Validate a Synor address.
#[wasm_bindgen(js_name = validateAddress)]
pub fn validate_address(address: &str) -> bool {
    decode_address(address).is_ok()
}

// Bech32m implementation

fn bech32m_encode(hrp: &str, data: &[u8]) -> Result<String, JsValue> {
    let mut result = String::with_capacity(hrp.len() + 1 + data.len() * 8 / 5 + 6);
    result.push_str(hrp);
    result.push('1');

    // Convert data to 5-bit groups
    let data5 = convert_bits(data, 8, 5, true)?;

    // Add data characters
    for &b in &data5 {
        result.push(CHARSET[b as usize] as char);
    }

    // Compute and add checksum
    let checksum = create_checksum(hrp, &data5);
    for b in checksum {
        result.push(CHARSET[b as usize] as char);
    }

    Ok(result)
}

fn bech32m_decode(address: &str) -> Result<(String, Vec<u8>), JsValue> {
    // Find separator
    let sep_pos = address
        .rfind('1')
        .ok_or_else(|| JsValue::from_str("Missing separator"))?;

    if sep_pos == 0 || sep_pos + 7 > address.len() {
        return Err(JsValue::from_str("Invalid address format"));
    }

    let hrp = &address[..sep_pos];
    let data_part = &address[sep_pos + 1..];

    // Convert characters to 5-bit values
    let mut data5 = Vec::with_capacity(data_part.len());
    for c in data_part.chars() {
        let idx = CHARSET
            .iter()
            .position(|&x| x as char == c.to_ascii_lowercase())
            .ok_or_else(|| JsValue::from_str("Invalid character in address"))?;
        data5.push(idx as u8);
    }

    // Verify checksum
    if !verify_checksum(hrp, &data5) {
        return Err(JsValue::from_str("Invalid checksum"));
    }

    // Remove checksum (last 6 bytes)
    let data5 = &data5[..data5.len() - 6];

    // Convert from 5-bit to 8-bit
    let data8 = convert_bits(data5, 5, 8, false)?;

    Ok((hrp.to_string(), data8))
}

fn convert_bits(data: &[u8], from_bits: u32, to_bits: u32, pad: bool) -> Result<Vec<u8>, JsValue> {
    let mut acc: u32 = 0;
    let mut bits: u32 = 0;
    let mut result = Vec::new();
    let maxv: u32 = (1 << to_bits) - 1;

    for &value in data {
        let v = value as u32;
        if v >> from_bits != 0 {
            return Err(JsValue::from_str("Invalid data value"));
        }
        acc = (acc << from_bits) | v;
        bits += from_bits;
        while bits >= to_bits {
            bits -= to_bits;
            result.push(((acc >> bits) & maxv) as u8);
        }
    }

    if pad {
        if bits > 0 {
            result.push(((acc << (to_bits - bits)) & maxv) as u8);
        }
    } else if bits >= from_bits || ((acc << (to_bits - bits)) & maxv) != 0 {
        return Err(JsValue::from_str("Invalid padding"));
    }

    Ok(result)
}

fn polymod(values: &[u8]) -> u32 {
    const GEN: [u32; 5] = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
    let mut chk: u32 = 1;
    for &v in values {
        let b = chk >> 25;
        chk = ((chk & 0x1ffffff) << 5) ^ (v as u32);
        for (i, &g) in GEN.iter().enumerate() {
            if (b >> i) & 1 != 0 {
                chk ^= g;
            }
        }
    }
    chk
}

fn hrp_expand(hrp: &str) -> Vec<u8> {
    let mut result = Vec::with_capacity(hrp.len() * 2 + 1);
    for c in hrp.chars() {
        result.push((c as u8) >> 5);
    }
    result.push(0);
    for c in hrp.chars() {
        result.push((c as u8) & 31);
    }
    result
}

fn create_checksum(hrp: &str, data: &[u8]) -> [u8; 6] {
    let mut values = hrp_expand(hrp);
    values.extend_from_slice(data);
    values.extend_from_slice(&[0, 0, 0, 0, 0, 0]);
    let poly = polymod(&values) ^ BECH32M_CONST;
    let mut result = [0u8; 6];
    for (i, item) in result.iter_mut().enumerate() {
        *item = ((poly >> (5 * (5 - i))) & 31) as u8;
    }
    result
}

fn verify_checksum(hrp: &str, data: &[u8]) -> bool {
    let mut values = hrp_expand(hrp);
    values.extend_from_slice(data);
    polymod(&values) == BECH32M_CONST
}

// Tests for WASM target only (uses JsValue)
#[cfg(all(test, target_arch = "wasm32"))]
mod tests {
    use super::*;

    #[test]
    fn test_address_encode() {
        let pubkey = [0u8; 32];
        let address = encode_address("mainnet", &pubkey).unwrap();
        assert!(address.starts_with("synor1"));

        let testnet_addr = encode_address("testnet", &pubkey).unwrap();
        assert!(testnet_addr.starts_with("tsynor1"));
    }

    #[test]
    fn test_bech32m_roundtrip() {
        let data = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
        let encoded = bech32m_encode("synor", &data).unwrap();
        let (hrp, decoded) = bech32m_decode(&encoded).unwrap();
        assert_eq!(hrp, "synor");
        assert_eq!(decoded, data);
    }

    #[test]
    fn test_invalid_address() {
        // Invalid character
        assert!(bech32m_decode("synor1invalid!").is_err());
        // Missing separator
        assert!(bech32m_decode("synoraddress").is_err());
    }
}

// Native tests that don't use JsValue
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
    use super::*;

    #[test]
    fn test_hrp_expand() {
        let expanded = hrp_expand("synor");
        assert!(!expanded.is_empty());
    }

    #[test]
    fn test_polymod() {
        let values = [1u8, 2, 3, 4, 5];
        let result = polymod(&values);
        assert!(result > 0);
    }

    #[test]
    fn test_checksum_creation() {
        let checksum = create_checksum("synor", &[1, 2, 3, 4, 5]);
        assert_eq!(checksum.len(), 6);
    }
}
