use std::str::FromStr;

use crate::bch::cashaddr::decode as bchaddr_decode;
use crate::bch::cashaddr::version_byte_flags as bch_flags;
use crate::chaindef::ScriptHash;
use crate::nexa::cashaddr::decode as nexaddr_decode;
use crate::nexa::cashaddr::version_byte_flags as nexa_flags;
use crate::nexa::transaction::TxOutType;
use anyhow::Result;
use bitcoincash::blockdata::opcodes;
use bitcoincash::blockdata::script::{Builder, Script};

pub fn decode_address(address: &str) -> Result<(Vec<u8>, u8)> {
    let bchaddr_decoded = bchaddr_decode(address);
    if let Ok(bchaddr) = bchaddr_decoded {
        return Ok((bchaddr.0, bchaddr.1));
    };
    let nexaddr_decoded = nexaddr_decode(address);
    if let Ok(nexaddr) = nexaddr_decoded {
        return Ok((nexaddr.0, nexaddr.1));
    };
    let mut legacy_decoded = bitcoincash::util::address::Address::from_str(address);
    if let Ok(addr) = legacy_decoded {
        match addr.address_type() {
            Some(bitcoincash::util::address::AddressType::P2pkh) => {
                return Ok((addr.payload.as_bytes().to_vec(), nexa_flags::TYPE_P2PKH))
            }
            Some(bitcoincash::util::address::AddressType::P2sh) => {
                return Ok((addr.payload.as_bytes().to_vec(), nexa_flags::TYPE_P2SH))
            }
            _ => {
                // p2wpkh, p2tr or some other btc stuff we don't implement
                legacy_decoded = Err(bitcoincash::util::address::Error::UnknownAddressType(
                    "Unsupported address type".to_string(),
                ));
            }
        }
    }
    let bchaddr_err = bchaddr_decoded.expect_err("expected decode error");
    let nexaddr_err = nexaddr_decoded.expect_err("expected decode error");
    let legacy_err = legacy_decoded.expect_err("expected decode error");
    Err(anyhow!("{:?}", (bchaddr_err, legacy_err, nexaddr_err)))
}

/**
 * Return scripthash of address *as it would be indexed by Rostrum*.
 * If there are token amounts encoded in address, these will be trimmed out.
 */
pub fn addr_to_scripthash(addr: &str) -> Result<ScriptHash> {
    let (payload, address_type) = decode_address(addr)?;

    assert_eq!(bch_flags::TYPE_P2PKH, nexa_flags::TYPE_P2PKH);
    assert_eq!(bch_flags::TYPE_P2SH, nexa_flags::TYPE_P2SH);

    let pubkey: Script = match address_type {
        bch_flags::TYPE_P2PKH | bch_flags::TYPE_P2PKH_TOKEN => Builder::new()
            .push_opcode(opcodes::all::OP_DUP)
            .push_opcode(opcodes::all::OP_HASH160)
            .push_slice(&payload)
            .push_opcode(opcodes::all::OP_EQUALVERIFY)
            .push_opcode(opcodes::all::OP_CHECKSIG)
            .into_script(),

        bch_flags::TYPE_P2SH | bch_flags::TYPE_P2SH_TOKEN => match payload.len() {
            20 => Builder::new()
                .push_opcode(opcodes::all::OP_HASH160)
                .push_slice(&payload)
                .push_opcode(opcodes::all::OP_EQUAL)
                .into_script(),
            32 => Builder::new()
                .push_opcode(opcodes::all::OP_HASH256)
                .push_slice(&payload)
                .push_opcode(opcodes::all::OP_EQUAL)
                .into_script(),
            _ => return Err(anyhow!("Unknown P2SH payload length of {}", payload.len())),
        },
        nexa_flags::TYPE_SCRIPT_TEMPLATE => {
            let script_pubkey = Script::from(payload.to_vec());
            let out = crate::nexa::transaction::TxOut {
                txout_type: TxOutType::TEMPLATE as u8,
                value: 0,
                script_pubkey,
            };
            match out.scriptpubkey_without_token() {
                Ok(Some(s)) => s,
                _ => {
                    // If we failed to trim a token out, just encoded at as we
                    // otherwise would.
                    out.script_pubkey
                }
            }
        }
        _ => return Err(anyhow!("Unknown address type {}", address_type)),
    };
    Ok(ScriptHash::from_script(&pubkey))
}

#[cfg(test)]
mod tests {
    use bitcoin_hashes::hex::{FromHex, ToHex};

    use super::*;

    #[test]
    fn test_addr_to_scripthash_p2pkh() {
        // Protocol specification test vector
        let scripthash = ScriptHash::from_hex(
            "8b01df4e368ea28f8dc0423bcf7a4923e3a12d307c875e47a0cfbf90b5c39161",
        )
        .unwrap();
        assert_eq!(
            scripthash,
            addr_to_scripthash("bitcoincash:qp3wjpa3tjlj042z2wv7hahsldgwhwy0rq9sywjpyy").unwrap()
        );

        assert_eq!(
            scripthash,
            addr_to_scripthash("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa").unwrap()
        );
    }

    #[test]
    fn test_addr_to_scripthash_p2sh() {
        // eatbch
        let scripthash = ScriptHash::from_hex(
            "829ce9ce75a8a8a01bf27a7365655506614ef0b8f5a7ecbef19093951a73b686",
        )
        .unwrap();
        assert_eq!(
            scripthash,
            addr_to_scripthash("bitcoincash:pp8skudq3x5hzw8ew7vzsw8tn4k8wxsqsv0lt0mf3g").unwrap()
        );
        assert_eq!(
            scripthash,
            addr_to_scripthash("38ty1qB68gHsiyZ8k3RPeCJ1wYQPrUCPPr").unwrap()
        );
    }

    #[test]
    fn test_addr_to_scripthash_garbage() {
        assert!(addr_to_scripthash("garbage").is_err());
    }

    #[test]
    fn test_to_le_hex() {
        let hex = "829ce9ce75a8a8a01bf27a7365655506614ef0b8f5a7ecbef19093951a73b686";
        let scripthash: ScriptHash = ScriptHash::from_hex(hex).unwrap();
        assert_eq!(hex, scripthash.to_hex());
    }
}
