use crate::{
    alloy_sol_types::{SolValue, Word, abi::token::WordToken, private::SolTypeValue, sol_data},
    prelude::*,
};

#[derive(
    Clone,
    Copy,
    Debug,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Hash,
    Encode,
    Decode,
    Default,
    TypeInfo,
    ReflectHash,
)]
#[codec(crate = crate::scale_codec)]
#[reflect_hash(crate = crate)]
#[annotate(sol_type = "address")]
pub struct Address(pub H160);

impl Address {
    pub const fn from_bytes(bytes: [u8; 20]) -> Self {
        Address(H160(bytes))
    }
}

impl SolValue for Address {
    type SolType = sol_data::Address;
}

impl SolTypeValue<sol_data::Address> for Address {
    #[inline]
    fn stv_to_tokens(&self) -> WordToken {
        let mut word = [0u8; 32];
        word[12..].copy_from_slice(self.0.as_fixed_bytes());
        WordToken(Word::from(word))
    }

    #[inline]
    fn stv_abi_encode_packed_to(&self, out: &mut Vec<u8>) {
        out.extend_from_slice(self.0.as_fixed_bytes());
    }

    #[inline]
    fn stv_eip712_data_word(&self) -> Word {
        <Address as SolTypeValue<sol_data::Address>>::stv_to_tokens(self).0
    }
}

impl SolTypeValue<sol_data::Address> for &Address {
    #[inline]
    fn stv_to_tokens(&self) -> WordToken {
        <Address as SolTypeValue<sol_data::Address>>::stv_to_tokens(self)
    }

    #[inline]
    fn stv_abi_encode_packed_to(&self, out: &mut Vec<u8>) {
        <Address as SolTypeValue<sol_data::Address>>::stv_abi_encode_packed_to(self, out)
    }

    #[inline]
    fn stv_eip712_data_word(&self) -> Word {
        <Address as SolTypeValue<sol_data::Address>>::stv_eip712_data_word(self)
    }
}

impl From<ActorId> for Address {
    fn from(value: ActorId) -> Self {
        Address(value.to_address_lossy())
    }
}

impl From<Address> for ActorId {
    fn from(value: Address) -> Self {
        ActorId::from(value.0)
    }
}

impl From<H160> for Address {
    fn from(value: H160) -> Self {
        Address(value)
    }
}

impl From<Address> for H160 {
    fn from(value: Address) -> Self {
        value.0
    }
}

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

impl From<u64> for Address {
    fn from(value: u64) -> Self {
        Address(ActorId::from(value).to_address_lossy())
    }
}

impl From<alloy_primitives::Address> for Address {
    fn from(value: alloy_primitives::Address) -> Self {
        Address(H160::from(value.0.0))
    }
}

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

    #[test]
    fn type_info_has_soltype_address_annotation() {
        use sails_type_registry::Registry;
        let mut registry = Registry::new();
        let ty = Address::type_def(&mut registry).expect("Address type is not registered");
        assert_eq!(ty.name, "Address");
        assert!(
            ty.annotations
                .iter()
                .any(|a| a.0 == "sol_type" && a.1.as_deref() == Some("address")),
            "expected @sol_type: address annotation on Address type"
        );
    }

    fn word_bytes(w: Word) -> [u8; 32] {
        w.0
    }

    fn addr_from(bytes20: [u8; 20]) -> Address {
        Address::from(bytes20)
    }

    #[test]
    fn stv_to_tokens_left_pad_12_zeros_and_copy_last_20_bytes() {
        let b20 = [
            0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
            0xff, 0x10, 0x20, 0x30, 0x40, 0x50,
        ];
        let a = addr_from(b20);

        let w = <Address as SolTypeValue<sol_data::Address>>::stv_eip712_data_word(&a);
        let bytes32 = word_bytes(w);

        assert_eq!(&bytes32[0..12], &[0u8; 12]);
        assert_eq!(&bytes32[12..32], &b20);
    }

    #[test]
    fn stv_abi_encode_packed_writes_exact_20_bytes_and_appends() {
        let b20 = [0xAB; 20];
        let a = addr_from(b20);

        let mut out = Vec::from([0x01, 0x02, 0x03]);
        <Address as SolTypeValue<sol_data::Address>>::stv_abi_encode_packed_to(&a, &mut out);

        assert_eq!(out.len(), 3 + 20);
        assert_eq!(&out[0..3], &[0x01, 0x02, 0x03]);
        assert_eq!(&out[3..], &b20);
    }

    #[test]
    fn stv_eip712_word_equals_to_tokens_word() {
        let b20 = [0x01; 20];
        let a = addr_from(b20);

        let w1 = <Address as SolTypeValue<sol_data::Address>>::stv_eip712_data_word(&a);
        let w2 = <Address as SolTypeValue<sol_data::Address>>::stv_to_tokens(&a).0;

        assert_eq!(word_bytes(w1), word_bytes(w2));
    }

    #[test]
    fn soltypevalue_for_ref_delegates_to_owned_impl() {
        let b20 = [0x7F; 20];
        let a = addr_from(b20);
        let aref = &a;

        let w_owned = <Address as SolTypeValue<sol_data::Address>>::stv_eip712_data_word(&a);
        let w_ref = <&Address as SolTypeValue<sol_data::Address>>::stv_eip712_data_word(&aref);
        assert_eq!(word_bytes(w_owned), word_bytes(w_ref));

        let mut o1 = Vec::new();
        let mut o2 = Vec::new();
        <Address as SolTypeValue<sol_data::Address>>::stv_abi_encode_packed_to(&a, &mut o1);
        <&Address as SolTypeValue<sol_data::Address>>::stv_abi_encode_packed_to(&aref, &mut o2);
        assert_eq!(o1, o2);

        let t_owned = <Address as SolTypeValue<sol_data::Address>>::stv_to_tokens(&a);
        let t_ref = <&Address as SolTypeValue<sol_data::Address>>::stv_to_tokens(&aref);
        assert_eq!(word_bytes(t_owned.0), word_bytes(t_ref.0));
    }

    #[test]
    fn from_address_to_actorid_and_back_is_identity_for_address() {
        let b20 = [0x42; 20];
        let a1 = addr_from(b20);

        let actor: ActorId = a1.into();
        let a2: Address = actor.into();

        assert_eq!(a2.0.as_fixed_bytes(), a1.0.as_fixed_bytes());
    }

    #[test]
    fn from_h160_and_into_h160_roundtrip() {
        let b20 = [0x13; 20];
        let h = H160::from(b20);

        let a: Address = h.into();
        let h2: H160 = a.into();

        assert_eq!(h2, H160::from(b20));
    }

    #[test]
    fn from_bytes20_constructs_expected_h160() {
        let b20 = [0x99; 20];
        let a = Address::from(b20);
        assert_eq!(a.0, H160::from(b20));
    }

    #[test]
    fn from_u64_matches_actorid_to_address_lossy() {
        let x: u64 = 123456789;
        let a1: Address = x.into();
        let a2 = Address(ActorId::from(x).to_address_lossy());
        assert_eq!(a1.0.as_fixed_bytes(), a2.0.as_fixed_bytes());
    }

    #[test]
    fn from_alloy_primitives_address_copies_bytes() {
        let b20 = [0xA5; 20];

        let alloy_addr = alloy_primitives::Address::from(b20);
        let a: Address = alloy_addr.into();

        assert_eq!(a.0.as_fixed_bytes(), &b20);
    }
}
