//! Address types for the Synor blockchain.
//!
//! Addresses use Bech32m encoding with network-specific prefixes:
//! - Mainnet: `synor1...`
//! - Testnet: `tsynor1...`
//! - Devnet: `dsynor1...`

use crate::{Hash256, Network};
use bech32::{Bech32m, Hrp};
use serde::{Deserialize, Serialize};
use std::fmt;
use thiserror::Error;

/// Address type indicator (first byte of address payload).
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[repr(u8)]
pub enum AddressType {
    /// Pay to Public Key Hash (Ed25519)
    P2PKH = 0x00,
    /// Pay to Public Key Hash (Hybrid: Ed25519 + Dilithium)
    P2pkhPqc = 0x01,
    /// Pay to Script Hash
    P2SH = 0x02,
    /// Pay to Script Hash (Post-Quantum)
    P2shPqc = 0x03,
}

impl AddressType {
    /// Creates an AddressType from a byte.
    pub fn from_byte(byte: u8) -> Result<Self, AddressError> {
        match byte {
            0x00 => Ok(AddressType::P2PKH),
            0x01 => Ok(AddressType::P2pkhPqc),
            0x02 => Ok(AddressType::P2SH),
            0x03 => Ok(AddressType::P2shPqc),
            _ => Err(AddressError::InvalidAddressType(byte)),
        }
    }

    /// Returns the byte representation.
    pub fn as_byte(&self) -> u8 {
        *self as u8
    }
}

/// A Synor blockchain address.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Address {
    /// The network this address belongs to.
    network: Network,
    /// The address type.
    addr_type: AddressType,
    /// The 32-byte payload (public key hash or script hash).
    payload: [u8; 32],
}

impl Address {
    /// Creates a new P2PKH address from an Ed25519 public key.
    pub fn from_ed25519_pubkey(network: Network, pubkey: &[u8; 32]) -> Self {
        let payload_hash = Hash256::blake3(pubkey);
        Address {
            network,
            addr_type: AddressType::P2PKH,
            payload: *payload_hash.as_bytes(),
        }
    }

    /// Creates a new hybrid P2PKH address from combined public keys.
    /// The payload is the hash of: ed25519_pubkey || dilithium_pubkey
    pub fn from_hybrid_pubkey(
        network: Network,
        ed25519_pubkey: &[u8; 32],
        dilithium_pubkey: &[u8],
    ) -> Self {
        let combined = Hash256::combine(&[ed25519_pubkey, dilithium_pubkey]);
        Address {
            network,
            addr_type: AddressType::P2pkhPqc,
            payload: *combined.as_bytes(),
        }
    }

    /// Creates a P2SH address from a script.
    pub fn from_script(network: Network, script: &[u8], post_quantum: bool) -> Self {
        let payload_hash = Hash256::blake3(script);
        Address {
            network,
            addr_type: if post_quantum {
                AddressType::P2shPqc
            } else {
                AddressType::P2SH
            },
            payload: *payload_hash.as_bytes(),
        }
    }

    /// Creates an address from raw components.
    pub fn from_parts(network: Network, addr_type: AddressType, payload: [u8; 32]) -> Self {
        Address {
            network,
            addr_type,
            payload,
        }
    }

    /// Parses an address from a Bech32m string.
    ///
    /// This is a convenience method that wraps the `FromStr` implementation.
    pub fn parse(s: &str) -> Result<Self, AddressError> {
        s.parse()
    }

    /// Encodes the address as a Bech32m string.
    pub fn to_bech32(&self) -> String {
        let hrp = Hrp::parse(self.network.hrp()).expect("Valid HRP");

        // Data: 1 byte type + 32 bytes payload
        let mut data = Vec::with_capacity(33);
        data.push(self.addr_type.as_byte());
        data.extend_from_slice(&self.payload);

        bech32::encode::<Bech32m>(hrp, &data).expect("Valid encoding")
    }

    /// Returns the network.
    pub fn network(&self) -> Network {
        self.network
    }

    /// Returns the address type.
    pub fn addr_type(&self) -> AddressType {
        self.addr_type
    }

    /// Returns the payload (public key hash or script hash).
    pub fn payload(&self) -> &[u8; 32] {
        &self.payload
    }

    /// Returns true if this is a post-quantum address.
    pub fn is_post_quantum(&self) -> bool {
        matches!(self.addr_type, AddressType::P2pkhPqc | AddressType::P2shPqc)
    }

    /// Returns a short representation of the address for display.
    pub fn short(&self) -> String {
        let full = self.to_bech32();
        let len = full.len();
        if len > 20 {
            format!("{}...{}", &full[..12], &full[len - 8..])
        } else {
            full
        }
    }
}

impl fmt::Debug for Address {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Address")
            .field("network", &self.network)
            .field("type", &self.addr_type)
            .field("bech32", &self.to_bech32())
            .finish()
    }
}

impl fmt::Display for Address {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.to_bech32())
    }
}

impl std::str::FromStr for Address {
    type Err = AddressError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // Decode Bech32m
        let (hrp, data) =
            bech32::decode(s).map_err(|e| AddressError::Bech32Error(e.to_string()))?;

        // Determine network from HRP
        let network = match hrp.as_str() {
            "synor" => Network::Mainnet,
            "tsynor" => Network::Testnet,
            "dsynor" => Network::Devnet,
            _ => return Err(AddressError::InvalidHrp(hrp.to_string())),
        };

        // Data should be 33 bytes: 1 byte type + 32 bytes payload
        if data.len() != 33 {
            return Err(AddressError::InvalidLength {
                expected: 33,
                got: data.len(),
            });
        }

        let addr_type = AddressType::from_byte(data[0])?;
        let mut payload = [0u8; 32];
        payload.copy_from_slice(&data[1..33]);

        Ok(Address {
            network,
            addr_type,
            payload,
        })
    }
}

impl Serialize for Address {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(&self.to_bech32())
    }
}

impl<'de> Deserialize<'de> for Address {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        Address::parse(&s).map_err(serde::de::Error::custom)
    }
}

impl borsh::BorshSerialize for Address {
    fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
        // Network (1 byte) + Type (1 byte) + Payload (32 bytes)
        writer.write_all(&[self.network as u8])?;
        writer.write_all(&[self.addr_type.as_byte()])?;
        writer.write_all(&self.payload)?;
        Ok(())
    }
}

impl borsh::BorshDeserialize for Address {
    fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
        let mut network_byte = [0u8; 1];
        reader.read_exact(&mut network_byte)?;
        let network = match network_byte[0] {
            0 => Network::Mainnet,
            1 => Network::Testnet,
            2 => Network::Devnet,
            _ => {
                return Err(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    "Invalid network byte",
                ))
            }
        };

        let mut type_byte = [0u8; 1];
        reader.read_exact(&mut type_byte)?;
        let addr_type = AddressType::from_byte(type_byte[0])
            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;

        let mut payload = [0u8; 32];
        reader.read_exact(&mut payload)?;

        Ok(Address {
            network,
            addr_type,
            payload,
        })
    }
}

/// Errors that can occur when working with addresses.
#[derive(Debug, Error)]
pub enum AddressError {
    #[error("Invalid address type: {0}")]
    InvalidAddressType(u8),

    #[error("Invalid HRP: {0}")]
    InvalidHrp(String),

    #[error("Invalid address length: expected {expected}, got {got}")]
    InvalidLength { expected: usize, got: usize },

    #[error("Bech32 error: {0}")]
    Bech32Error(String),
}

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

    #[test]
    fn test_address_roundtrip() {
        let pubkey = [42u8; 32];
        let addr = Address::from_ed25519_pubkey(Network::Mainnet, &pubkey);

        let encoded = addr.to_bech32();
        assert!(encoded.starts_with("synor1"));

        let decoded = Address::parse(&encoded).unwrap();
        assert_eq!(addr, decoded);
    }

    #[test]
    fn test_address_networks() {
        let pubkey = [1u8; 32];

        let mainnet = Address::from_ed25519_pubkey(Network::Mainnet, &pubkey);
        assert!(mainnet.to_bech32().starts_with("synor1"));

        let testnet = Address::from_ed25519_pubkey(Network::Testnet, &pubkey);
        assert!(testnet.to_bech32().starts_with("tsynor1"));

        let devnet = Address::from_ed25519_pubkey(Network::Devnet, &pubkey);
        assert!(devnet.to_bech32().starts_with("dsynor1"));
    }

    #[test]
    fn test_hybrid_address() {
        let ed_pubkey = [1u8; 32];
        let dilithium_pubkey = vec![2u8; 1312]; // Dilithium3 public key size

        let addr = Address::from_hybrid_pubkey(Network::Mainnet, &ed_pubkey, &dilithium_pubkey);

        assert!(addr.is_post_quantum());
        assert_eq!(addr.addr_type(), AddressType::P2pkhPqc);
    }

    #[test]
    fn test_address_short() {
        let pubkey = [42u8; 32];
        let addr = Address::from_ed25519_pubkey(Network::Mainnet, &pubkey);
        let short = addr.short();
        assert!(short.contains("..."));
    }
}
