use bitcoin::ScriptBuf;
use brk_error::Error;
use byteview::ByteView;
use derive_more::Deref;
use vecdb::Bytes;

use super::{AddrBytes, OutputType};

#[derive(Debug, Deref, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Bytes, Hash)]
pub struct AddrHash(u64);

impl AddrHash {
    pub const fn new(value: u64) -> Self {
        Self(value)
    }

    #[inline]
    pub fn from_script(script: &ScriptBuf, output_type: OutputType) -> Result<Self, Error> {
        Ok(Self(rapidhash::v3::rapidhash_v3(
            AddrBytes::script_payload(script, output_type)?,
        )))
    }
}

impl From<&AddrBytes> for AddrHash {
    #[inline]
    fn from(addr_bytes: &AddrBytes) -> Self {
        Self(addr_bytes.hash())
    }
}

impl From<ByteView> for AddrHash {
    #[inline]
    fn from(value: ByteView) -> Self {
        Self(u64::from_be_bytes((&*value).try_into().unwrap()))
    }
}
impl From<AddrHash> for ByteView {
    #[inline]
    fn from(value: AddrHash) -> Self {
        Self::from(&value)
    }
}
impl From<&AddrHash> for ByteView {
    #[inline]
    fn from(value: &AddrHash) -> Self {
        Self::new(&value.0.to_be_bytes())
    }
}

#[cfg(test)]
mod tests {
    use super::AddrHash;
    use crate::{
        AddrBytes, OutputType, P2ABytes, P2PK33Bytes, P2PK65Bytes, P2PKHBytes, P2SHBytes,
        P2TRBytes, P2WPKHBytes, P2WSHBytes,
    };

    #[test]
    fn hashes_borrowed_script_payloads_like_owned_addresses() {
        let addresses = [
            AddrBytes::from(P2PK65Bytes::from(&[0x04; 65][..])),
            AddrBytes::from(P2PK33Bytes::from(&[0x02; 33][..])),
            AddrBytes::from(P2PKHBytes::from(&[0x03; 20][..])),
            AddrBytes::from(P2SHBytes::from(&[0x04; 20][..])),
            AddrBytes::from(P2WPKHBytes::from(&[0x05; 20][..])),
            AddrBytes::from(P2WSHBytes::from(&[0x06; 32][..])),
            AddrBytes::from(P2TRBytes::from(&[0x07; 32][..])),
            AddrBytes::from(P2ABytes::from(&[78, 115][..])),
        ];

        for address in addresses {
            let script = address.to_script_pubkey();
            let output_type = OutputType::from(&script);

            assert_eq!(
                AddrHash::from_script(&script, output_type).unwrap(),
                AddrHash::from(&address)
            );
            assert_eq!(
                AddrBytes::try_from((&script, output_type)).unwrap(),
                address
            );
        }
    }
}
