extern crate alloc;
extern crate core;
pub mod caches;
mod data_stack;
pub mod opcodes;
pub mod script_builder;
pub mod script_class;
pub mod standard;
use crate::caches::Cache;
use crate::data_stack::{DataStack, Stack};
use crate::opcodes::{deserialize_next_opcode, OpCodeImplementation};
use itertools::Itertools;
use kaspa_consensus_core::hashing::sighash::{calc_ecdsa_signature_hash, calc_schnorr_signature_hash, SigHashReusedValues};
use kaspa_consensus_core::hashing::sighash_type::SigHashType;
use kaspa_consensus_core::tx::{ScriptPublicKey, TransactionInput, UtxoEntry, VerifiableTransaction};
use kaspa_txscript_errors::TxScriptError;
use log::trace;
use opcodes::codes::OpReturn;
use opcodes::{codes, to_small_int, OpCond};
use script_class::ScriptClass;
pub mod prelude {
    pub use super::standard::*;
}
pub use standard::*;
pub const MAX_SCRIPT_PUBLIC_KEY_VERSION: u16 = 0;
pub const MAX_STACK_SIZE: usize = 244;
pub const MAX_SCRIPTS_SIZE: usize = 10_000;
pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520;
pub const MAX_OPS_PER_SCRIPT: i32 = 201;
pub const MAX_TX_IN_SEQUENCE_NUM: u64 = u64::MAX;
pub const SEQUENCE_LOCK_TIME_DISABLED: u64 = 1 << 63;
pub const SEQUENCE_LOCK_TIME_MASK: u64 = 0x00000000ffffffff;
pub const LOCK_TIME_THRESHOLD: u64 = 500_000_000_000;
pub const MAX_PUB_KEYS_PER_MUTLTISIG: i32 = 20;
pub const NO_COST_OPCODE: u8 = 0x60;
#[derive(Clone, Hash, PartialEq, Eq)]
enum Signature {
    Secp256k1(secp256k1::schnorr::Signature),
    Ecdsa(secp256k1::ecdsa::Signature),
}
#[derive(Clone, Hash, PartialEq, Eq)]
enum PublicKey {
    Schnorr(secp256k1::XOnlyPublicKey),
    Ecdsa(secp256k1::PublicKey),
}
#[derive(Clone, Hash, PartialEq, Eq)]
pub struct SigCacheKey {
    signature: Signature,
    pub_key: PublicKey,
    message: secp256k1::Message,
}
enum ScriptSource<'a, T: VerifiableTransaction> {
    TxInput { tx: &'a T, input: &'a TransactionInput, id: usize, utxo_entry: &'a UtxoEntry, is_p2sh: bool },
    StandAloneScripts(Vec<&'a [u8]>),
}
pub struct TxScriptEngine<'a, T: VerifiableTransaction> {
    dstack: Stack,
    astack: Stack,
    script_source: ScriptSource<'a, T>,
    reused_values: &'a mut SigHashReusedValues,
    sig_cache: &'a Cache<SigCacheKey, bool>,
    cond_stack: Vec<OpCond>, num_ops: i32,
}
fn parse_script<T: VerifiableTransaction>(
    script: &[u8],
) -> impl Iterator<Item = Result<Box<dyn OpCodeImplementation<T>>, TxScriptError>> + '_ {
    script.iter().batching(|it| deserialize_next_opcode(it))
}
pub fn get_sig_op_count<T: VerifiableTransaction>(signature_script: &[u8], prev_script_public_key: &ScriptPublicKey) -> u64 {
    let is_p2sh = ScriptClass::is_pay_to_script_hash(prev_script_public_key.script());
    let script_pub_key_ops = parse_script::<T>(prev_script_public_key.script()).collect_vec();
    if !is_p2sh {
        return get_sig_op_count_by_opcodes(&script_pub_key_ops);
    }
    let signature_script_ops = parse_script::<T>(signature_script).collect_vec();
    if signature_script_ops.is_empty() || signature_script_ops.iter().any(|op| op.is_err() || !op.as_ref().unwrap().is_push_opcode()) {
        return 0;
    }
    let p2sh_script = signature_script_ops.last().expect("checked if empty above").as_ref().expect("checked if err above").get_data();
    let p2sh_ops = parse_script::<T>(p2sh_script).collect_vec();
    get_sig_op_count_by_opcodes(&p2sh_ops)
}
fn get_sig_op_count_by_opcodes<T: VerifiableTransaction>(opcodes: &[Result<Box<dyn OpCodeImplementation<T>>, TxScriptError>]) -> u64 {
    let mut num_sigs: u64 = 0;
    for (i, op) in opcodes.iter().enumerate() {
        match op {
            Ok(op) => {
                match op.value() {
                    codes::OpCheckSig | codes::OpCheckSigVerify | codes::OpCheckSigECDSA => num_sigs += 1,
                    codes::OpCheckMultiSig | codes::OpCheckMultiSigVerify | codes::OpCheckMultiSigECDSA => {
                        if i == 0 {
                            num_sigs += MAX_PUB_KEYS_PER_MUTLTISIG as u64;
                            continue;
                        }
                        let prev_opcode = opcodes[i - 1].as_ref().expect("they were checked before");
                        if prev_opcode.value() >= codes::OpTrue && prev_opcode.value() <= codes::Op16 {
                            num_sigs += to_small_int(prev_opcode) as u64;
                        } else {
                            num_sigs += MAX_PUB_KEYS_PER_MUTLTISIG as u64;
                        }
                    }
                    _ => {} }
            }
            Err(_) => return num_sigs,
        }
    }
    num_sigs
}
pub fn is_unspendable<T: VerifiableTransaction>(script: &[u8]) -> bool {
    parse_script::<T>(script).enumerate().any(|(index, op)| op.is_err() || (index == 0 && op.unwrap().value() == OpReturn))
}
impl<'a, T: VerifiableTransaction> TxScriptEngine<'a, T> {
    pub fn new(reused_values: &'a mut SigHashReusedValues, sig_cache: &'a Cache<SigCacheKey, bool>) -> Self {
        Self {
            dstack: vec![],
            astack: vec![],
            script_source: ScriptSource::StandAloneScripts(vec![]),
            reused_values,
            sig_cache,
            cond_stack: vec![],
            num_ops: 0,
        }
    }
    pub fn from_transaction_input(
        tx: &'a T,
        input: &'a TransactionInput,
        input_idx: usize,
        utxo_entry: &'a UtxoEntry,
        reused_values: &'a mut SigHashReusedValues,
        sig_cache: &'a Cache<SigCacheKey, bool>,
    ) -> Result<Self, TxScriptError> {
        let script_public_key = utxo_entry.script_public_key.script();
        let is_p2sh = ScriptClass::is_pay_to_script_hash(script_public_key);
        match input_idx < tx.tx().inputs.len() {
            true => Ok(Self {
                dstack: Default::default(),
                astack: Default::default(),
                script_source: ScriptSource::TxInput { tx, input, id: input_idx, utxo_entry, is_p2sh },
                reused_values,
                sig_cache,
                cond_stack: Default::default(),
                num_ops: 0,
            }),
            false => Err(TxScriptError::InvalidIndex(input_idx, tx.tx().inputs.len())),
        }
    }
    pub fn from_script(script: &'a [u8], reused_values: &'a mut SigHashReusedValues, sig_cache: &'a Cache<SigCacheKey, bool>) -> Self {
        Self {
            dstack: Default::default(),
            astack: Default::default(),
            script_source: ScriptSource::StandAloneScripts(vec![script]),
            reused_values,
            sig_cache,
            cond_stack: Default::default(),
            num_ops: 0,
        }
    }
    #[inline]
    pub fn is_executing(&self) -> bool {
        return self.cond_stack.is_empty() || *self.cond_stack.last().expect("Checked not empty") == OpCond::True;
    }
    fn execute_opcode(&mut self, opcode: Box<dyn OpCodeImplementation<T>>) -> Result<(), TxScriptError> {
        if !opcode.is_push_opcode() {
            self.num_ops += 1;
            if self.num_ops > MAX_OPS_PER_SCRIPT {
                return Err(TxScriptError::TooManyOperations(MAX_OPS_PER_SCRIPT));
            }
        } else if opcode.len() > MAX_SCRIPT_ELEMENT_SIZE {
            return Err(TxScriptError::ElementTooBig(opcode.len(), MAX_SCRIPT_ELEMENT_SIZE));
        }
        if self.is_executing() || opcode.is_conditional() {
            if opcode.value() > 0 && opcode.value() <= 0x4e {
                opcode.check_minimal_data_push()?;
            }
            opcode.execute(self)
        } else {
            Ok(())
        }
    }
    fn execute_script(&mut self, script: &[u8], verify_only_push: bool) -> Result<(), TxScriptError> {
        let script_result = parse_script(script).try_for_each(|opcode| {
            let opcode = opcode?;
            if opcode.is_disabled() {
                return Err(TxScriptError::OpcodeDisabled(format!("{:?}", opcode)));
            }
            if opcode.always_illegal() {
                return Err(TxScriptError::OpcodeReserved(format!("{:?}", opcode)));
            }
            if verify_only_push && !opcode.is_push_opcode() {
                return Err(TxScriptError::SignatureScriptNotPushOnly);
            }
            self.execute_opcode(opcode)?;
            let combined_size = self.astack.len() + self.dstack.len();
            if combined_size > MAX_STACK_SIZE {
                return Err(TxScriptError::StackSizeExceeded(combined_size, MAX_STACK_SIZE));
            }
            Ok(())
        });
        if script_result.is_ok() && !self.cond_stack.is_empty() {
            return Err(TxScriptError::ErrUnbalancedConditional);
        }
        self.astack.clear();
        self.num_ops = 0; script_result
    }
    pub fn execute(&mut self) -> Result<(), TxScriptError> {
        let (scripts, is_p2sh) = match &self.script_source {
            ScriptSource::TxInput { input, utxo_entry, is_p2sh, .. } => {
                if utxo_entry.script_public_key.version() > MAX_SCRIPT_PUBLIC_KEY_VERSION {
                    trace!("The version of the scriptPublicKey is higher than the known version - the Execute function returns true.");
                    return Ok(());
                }
                (vec![input.signature_script.as_slice(), utxo_entry.script_public_key.script()], *is_p2sh)
            }
            ScriptSource::StandAloneScripts(scripts) => (scripts.clone(), false),
        };
        if scripts.is_empty() {
            return Err(TxScriptError::NoScripts);
        }
        if scripts.iter().all(|e| e.is_empty()) {
            return Err(TxScriptError::EvalFalse);
        }
        if let Some(s) = scripts.iter().find(|e| e.len() > MAX_SCRIPTS_SIZE) {
            return Err(TxScriptError::ScriptSize(s.len(), MAX_SCRIPTS_SIZE));
        }
        let mut saved_stack: Option<Vec<Vec<u8>>> = None;
        scripts.iter().enumerate().filter(|(_, s)| !s.is_empty()).try_for_each(|(idx, s)| {
            let verify_only_push =
                idx == 0 && matches!(self.script_source, ScriptSource::TxInput { tx: _, input: _, id: _, utxo_entry: _, is_p2sh: _ });
            if is_p2sh && idx == 1 {
                saved_stack = Some(self.dstack.clone());
            }
            self.execute_script(s, verify_only_push)
        })?;
        if is_p2sh {
            self.check_error_condition(false)?;
            self.dstack = saved_stack.ok_or(TxScriptError::EmptyStack)?;
            let script = self.dstack.pop().ok_or(TxScriptError::EmptyStack)?;
            self.execute_script(script.as_slice(), false)?
        }
        self.check_error_condition(true)?;
        Ok(())
    }
    #[inline]
    fn check_error_condition(&mut self, final_script: bool) -> Result<(), TxScriptError> {
        if final_script {
            if self.dstack.len() > 1 {
                return Err(TxScriptError::CleanStack(self.dstack.len() - 1));
            } else if self.dstack.is_empty() {
                return Err(TxScriptError::EmptyStack);
            }
        }
        let [v]: [bool; 1] = self.dstack.pop_items()?;
        match v {
            true => Ok(()),
            false => Err(TxScriptError::EvalFalse),
        }
    }
    fn check_pub_key_encoding(pub_key: &[u8]) -> Result<(), TxScriptError> {
        match pub_key.len() {
            32 => Ok(()),
            _ => Err(TxScriptError::PubKeyFormat),
        }
    }
    fn check_pub_key_encoding_ecdsa(pub_key: &[u8]) -> Result<(), TxScriptError> {
        match pub_key.len() {
            33 => Ok(()),
            _ => Err(TxScriptError::PubKeyFormat),
        }
    }
    fn op_check_multisig_schnorr_or_ecdsa(&mut self, ecdsa: bool) -> Result<(), TxScriptError> {
        let [num_keys]: [i32; 1] = self.dstack.pop_items()?;
        if num_keys < 0 {
            return Err(TxScriptError::InvalidPubKeyCount(format!("number of pubkeys {num_keys} is negative")));
        } else if num_keys > MAX_PUB_KEYS_PER_MUTLTISIG {
            return Err(TxScriptError::InvalidPubKeyCount(format!("too many pubkeys {num_keys} > {MAX_PUB_KEYS_PER_MUTLTISIG}")));
        }
        let num_keys_usize = num_keys as usize;
        self.num_ops += num_keys;
        if self.num_ops > MAX_OPS_PER_SCRIPT {
            return Err(TxScriptError::TooManyOperations(MAX_OPS_PER_SCRIPT));
        }
        let pub_keys = match self.dstack.len() >= num_keys_usize {
            true => self.dstack.split_off(self.dstack.len() - num_keys_usize),
            false => return Err(TxScriptError::InvalidStackOperation(num_keys_usize, self.dstack.len())),
        };
        let [num_sigs]: [i32; 1] = self.dstack.pop_items()?;
        if num_sigs < 0 {
            return Err(TxScriptError::InvalidSignatureCount(format!("number of signatures {num_sigs} is negative")));
        } else if num_sigs > num_keys {
            return Err(TxScriptError::InvalidSignatureCount(format!("more signatures than pubkeys {num_sigs} > {num_keys}")));
        }
        let num_sigs = num_sigs as usize;
        let signatures = match self.dstack.len() >= num_sigs {
            true => self.dstack.split_off(self.dstack.len() - num_sigs),
            false => return Err(TxScriptError::InvalidStackOperation(num_sigs, self.dstack.len())),
        };
        let mut failed = false;
        let mut pub_key_iter = pub_keys.iter();
        'outer: for (sig_idx, signature) in signatures.iter().enumerate() {
            if signature.is_empty() {
                failed = true;
                break;
            }
            let typ = *signature.last().expect("checked that is not empty");
            let signature = &signature[..signature.len() - 1];
            let hash_type = SigHashType::from_u8(typ).map_err(|_| TxScriptError::InvalidSigHashType(typ))?;
            loop {
                if pub_key_iter.len() < num_sigs - sig_idx {
                    failed = true;
                    break 'outer; }
                let pub_key = pub_key_iter.next().unwrap();
                let check_signature_result = if ecdsa {
                    self.check_ecdsa_signature(hash_type, pub_key.as_slice(), signature)
                } else {
                    self.check_schnorr_signature(hash_type, pub_key.as_slice(), signature)
                };
                match check_signature_result {
                    Ok(valid) => {
                        if valid {
                            break;
                        }
                    }
                    Err(e) => {
                        return Err(e);
                    }
                }
            }
        }
        if failed && signatures.iter().any(|sig| !sig.is_empty()) {
            return Err(TxScriptError::NullFail);
        }
        self.dstack.push_item(!failed);
        Ok(())
    }
    #[inline]
    fn check_schnorr_signature(&mut self, hash_type: SigHashType, key: &[u8], sig: &[u8]) -> Result<bool, TxScriptError> {
        match self.script_source {
            ScriptSource::TxInput { tx, id, .. } => {
                if sig.len() != 64 {
                    return Err(TxScriptError::SigLength(sig.len()));
                }
                Self::check_pub_key_encoding(key)?;
                let pk = secp256k1::XOnlyPublicKey::from_slice(key).map_err(TxScriptError::InvalidSignature)?;
                let sig = secp256k1::schnorr::Signature::from_slice(sig).map_err(TxScriptError::InvalidSignature)?;
                let sig_hash = calc_schnorr_signature_hash(tx, id, hash_type, self.reused_values);
                let msg = secp256k1::Message::from_slice(sig_hash.as_bytes().as_slice()).unwrap();
                let sig_cache_key =
                    SigCacheKey { signature: Signature::Secp256k1(sig), pub_key: PublicKey::Schnorr(pk), message: msg };
                match self.sig_cache.get(&sig_cache_key) {
                    Some(valid) => Ok(valid),
                    None => {
                        match sig.verify(&msg, &pk) {
                            Ok(()) => {
                                self.sig_cache.insert(sig_cache_key, true);
                                Ok(true)
                            }
                            Err(_) => {
                                self.sig_cache.insert(sig_cache_key, false);
                                Ok(false)
                            }
                        }
                    }
                }
            }
            _ => Err(TxScriptError::NotATransactionInput),
        }
    }
    fn check_ecdsa_signature(&mut self, hash_type: SigHashType, key: &[u8], sig: &[u8]) -> Result<bool, TxScriptError> {
        match self.script_source {
            ScriptSource::TxInput { tx, id, .. } => {
                if sig.len() != 64 {
                    return Err(TxScriptError::SigLength(sig.len()));
                }
                Self::check_pub_key_encoding_ecdsa(key)?;
                let pk = secp256k1::PublicKey::from_slice(key).map_err(TxScriptError::InvalidSignature)?;
                let sig = secp256k1::ecdsa::Signature::from_compact(sig).map_err(TxScriptError::InvalidSignature)?;
                let sig_hash = calc_ecdsa_signature_hash(tx, id, hash_type, self.reused_values);
                let msg = secp256k1::Message::from_slice(sig_hash.as_bytes().as_slice()).unwrap();
                let sig_cache_key = SigCacheKey { signature: Signature::Ecdsa(sig), pub_key: PublicKey::Ecdsa(pk), message: msg };
                match self.sig_cache.get(&sig_cache_key) {
                    Some(valid) => Ok(valid),
                    None => {
                        match sig.verify(&msg, &pk) {
                            Ok(()) => {
                                self.sig_cache.insert(sig_cache_key, true);
                                Ok(true)
                            }
                            Err(_) => {
                                self.sig_cache.insert(sig_cache_key, false);
                                Ok(false)
                            }
                        }
                    }
                }
            }
            _ => Err(TxScriptError::NotATransactionInput),
        }
    }
}
#[cfg(test)]
mod tests {
    use std::iter::once;
    use crate::opcodes::codes::{OpBlake2b, OpCheckSig, OpData1, OpData2, OpData32, OpDup, OpEqual, OpPushData1, OpTrue};
    use super::*;
    use kaspa_consensus_core::tx::{
        PopulatedTransaction, ScriptPublicKey, Transaction, TransactionId, TransactionOutpoint, TransactionOutput,
    };
    use smallvec::SmallVec;
    struct ScriptTestCase {
        script: &'static [u8],
        expected_result: Result<(), TxScriptError>,
    }
    struct KeyTestCase {
        name: &'static str,
        key: &'static [u8],
        is_valid: bool,
    }
    struct VerifiableTransactionMock {}
    impl VerifiableTransaction for VerifiableTransactionMock {
        fn tx(&self) -> &Transaction {
            unimplemented!()
        }
        fn populated_input(&self, _index: usize) -> (&TransactionInput, &UtxoEntry) {
            unimplemented!()
        }
    }
    fn run_test_script_cases(test_cases: Vec<ScriptTestCase>) {
        let sig_cache = Cache::new(10_000);
        let mut reused_values = SigHashReusedValues::new();
        for test in test_cases {
            let input = TransactionInput {
                previous_outpoint: TransactionOutpoint {
                    transaction_id: TransactionId::from_bytes([
                        0xc9, 0x97, 0xa5, 0xe5, 0x6e, 0x10, 0x41, 0x02, 0xfa, 0x20, 0x9c, 0x6a, 0x85, 0x2d, 0xd9, 0x06, 0x60, 0xa2,
                        0x0b, 0x2d, 0x9c, 0x35, 0x24, 0x23, 0xed, 0xce, 0x25, 0x85, 0x7f, 0xcd, 0x37, 0x04,
                    ]),
                    index: 0,
                },
                signature_script: vec![],
                sequence: 4294967295,
                sig_op_count: 0,
            };
            let output = TransactionOutput { value: 1000000000, script_public_key: ScriptPublicKey::new(0, test.script.into()) };
            let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]);
            let utxo_entry = UtxoEntry::new(output.value, output.script_public_key.clone(), 0, tx.is_coinbase());
            let populated_tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]);
            let mut vm = TxScriptEngine::from_transaction_input(&populated_tx, &input, 0, &utxo_entry, &mut reused_values, &sig_cache)
                .expect("Script creation failed");
            assert_eq!(vm.execute(), test.expected_result);
        }
    }
    #[test]
    fn test_check_error_condition() {
        let test_cases = vec![
            ScriptTestCase {
                script: b"\x51", expected_result: Ok(()),
            },
            ScriptTestCase {
                script: b"\x61", expected_result: Err(TxScriptError::EmptyStack),
            },
            ScriptTestCase {
                script: b"\x51\x51", expected_result: Err(TxScriptError::CleanStack(1)),
            },
            ScriptTestCase {
                script: b"\x00", expected_result: Err(TxScriptError::EvalFalse),
            },
        ];
        run_test_script_cases(test_cases)
    }
    #[test]
    fn test_check_opif() {
        let test_cases = vec![
            ScriptTestCase {
                script: b"\x63", expected_result: Err(TxScriptError::EmptyStack),
            },
            ScriptTestCase {
                script: b"\x52\x63", expected_result: Err(TxScriptError::InvalidState("expected boolean".to_string())),
            },
            ScriptTestCase {
                script: b"\x51\x63", expected_result: Err(TxScriptError::ErrUnbalancedConditional),
            },
            ScriptTestCase {
                script: b"\x00\x63", expected_result: Err(TxScriptError::ErrUnbalancedConditional),
            },
            ScriptTestCase {
                script: b"\x51\x63\x51\x68", expected_result: Ok(()),
            },
            ScriptTestCase {
                script: b"\x00\x63\x51\x68", expected_result: Err(TxScriptError::EmptyStack),
            },
        ];
        run_test_script_cases(test_cases)
    }
    #[test]
    fn test_check_opelse() {
        let test_cases = vec![
            ScriptTestCase {
                script: b"\x67", expected_result: Err(TxScriptError::InvalidState("condition stack empty".to_string())),
            },
            ScriptTestCase {
                script: b"\x51\x63\x67", expected_result: Err(TxScriptError::ErrUnbalancedConditional),
            },
            ScriptTestCase {
                script: b"\x00\x63\x67", expected_result: Err(TxScriptError::ErrUnbalancedConditional),
            },
            ScriptTestCase {
                script: b"\x51\x63\x51\x67\x68", expected_result: Ok(()),
            },
            ScriptTestCase {
                script: b"\x00\x63\x67\x51\x68", expected_result: Ok(()),
            },
        ];
        run_test_script_cases(test_cases)
    }
    #[test]
    fn test_check_opnotif() {
        let test_cases = vec![
            ScriptTestCase {
                script: b"\x64", expected_result: Err(TxScriptError::EmptyStack),
            },
            ScriptTestCase {
                script: b"\x51\x64", expected_result: Err(TxScriptError::ErrUnbalancedConditional),
            },
            ScriptTestCase {
                script: b"\x00\x64", expected_result: Err(TxScriptError::ErrUnbalancedConditional),
            },
            ScriptTestCase {
                script: b"\x51\x64\x67\x51\x68", expected_result: Ok(()),
            },
            ScriptTestCase {
                script: b"\x51\x64\x51\x67\x00\x68", expected_result: Err(TxScriptError::EvalFalse),
            },
            ScriptTestCase {
                script: b"\x00\x64\x51\x68", expected_result: Ok(()),
            },
        ];
        run_test_script_cases(test_cases)
    }
    #[test]
    fn test_check_nestedif() {
        let test_cases = vec![
            ScriptTestCase {
                script: b"\x51\x63\x00\x67\x51\x63\x51\x68\x68", expected_result: Err(TxScriptError::EvalFalse),
            },
            ScriptTestCase {
                script: b"\x51\x63\x00\x67\x00\x63\x67\x51\x68\x68", expected_result: Err(TxScriptError::EvalFalse),
            },
            ScriptTestCase {
                script: b"\x51\x64\x00\x67\x51\x63\x51\x68\x68", expected_result: Ok(()),
            },
            ScriptTestCase {
                script: b"\x51\x64\x00\x67\x00\x63\x67\x51\x68\x68", expected_result: Ok(()),
            },
            ScriptTestCase {
                script: b"\x51\x64\x00\x67\x00\x64\x00\x67\x51\x68\x68", expected_result: Err(TxScriptError::EvalFalse),
            },
            ScriptTestCase {
                script: b"\x51\x00\x63\x63\x00\x68\x68", expected_result: Ok(()),
            },
            ScriptTestCase {
                script: b"\x51\x00\x63\x63\x63\x00\x67\x00\x68\x68\x68", expected_result: Ok(()),
            },
            ScriptTestCase {
                script: b"\x51\x00\x63\x63\x63\x63\x00\x67\x00\x68\x68\x68\x68", expected_result: Ok(()),
            },
        ];
        run_test_script_cases(test_cases)
    }
    #[test]
    fn test_check_pub_key_encode() {
        let test_cases = vec![
            KeyTestCase {
                name: "uncompressed - invalid",
                key: &[
                    0x04u8, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, 0x01, 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, 0xb6,
                    0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, 0x90, 0x9a, 0x5c, 0xb2, 0xe0, 0xea, 0xdd,
                    0xfb, 0x84, 0xcc, 0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f,
                    0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, 0xb4, 0x12, 0xa3,
                ],
                is_valid: false,
            },
            KeyTestCase {
                name: "compressed - invalid",
                key: &[
                    0x02, 0xce, 0x0b, 0x14, 0xfb, 0x84, 0x2b, 0x1b, 0xa5, 0x49, 0xfd, 0xd6, 0x75, 0xc9, 0x80, 0x75, 0xf1, 0x2e, 0x9c,
                    0x51, 0x0f, 0x8e, 0xf5, 0x2b, 0xd0, 0x21, 0xa9, 0xa1, 0xf4, 0x80, 0x9d, 0x3b, 0x4d,
                ],
                is_valid: false,
            },
            KeyTestCase {
                name: "compressed - invalid",
                key: &[
                    0x03, 0x26, 0x89, 0xc7, 0xc2, 0xda, 0xb1, 0x33, 0x09, 0xfb, 0x14, 0x3e, 0x0e, 0x8f, 0xe3, 0x96, 0x34, 0x25, 0x21,
                    0x88, 0x7e, 0x97, 0x66, 0x90, 0xb6, 0xb4, 0x7f, 0x5b, 0x2a, 0x4b, 0x7d, 0x44, 0x8e,
                ],
                is_valid: false,
            },
            KeyTestCase {
                name: "hybrid - invalid",
                key: &[
                    0x06, 0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce, 0x87, 0x0b, 0x07, 0x02, 0x9b,
                    0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98, 0x48, 0x3a, 0xda, 0x77, 0x26,
                    0xa3, 0xc4, 0x65, 0x5d, 0xa4, 0xfb, 0xfc, 0x0e, 0x11, 0x08, 0xa8, 0xfd, 0x17, 0xb4, 0x48, 0xa6, 0x85, 0x54, 0x19,
                    0x9c, 0x47, 0xd0, 0x8f, 0xfb, 0x10, 0xd4, 0xb8,
                ],
                is_valid: false,
            },
            KeyTestCase {
                name: "32 bytes pubkey - Ok",
                key: &[
                    0x26, 0x89, 0xc7, 0xc2, 0xda, 0xb1, 0x33, 0x09, 0xfb, 0x14, 0x3e, 0x0e, 0x8f, 0xe3, 0x96, 0x34, 0x25, 0x21, 0x88,
                    0x7e, 0x97, 0x66, 0x90, 0xb6, 0xb4, 0x7f, 0x5b, 0x2a, 0x4b, 0x7d, 0x44, 0x8e,
                ],
                is_valid: true,
            },
            KeyTestCase { name: "empty", key: &[], is_valid: false },
        ];
        for test in test_cases {
            let check = TxScriptEngine::<PopulatedTransaction>::check_pub_key_encoding(test.key);
            if test.is_valid {
                assert_eq!(
                    check,
                    Ok(()),
                    "checkSignatureLength test '{}' failed when it should have succeeded: {:?}",
                    test.name,
                    check
                )
            } else {
                assert_eq!(
                    check,
                    Err(TxScriptError::PubKeyFormat),
                    "checkSignatureEncoding test '{}' succeeded or failed on wrong format ({:?})",
                    test.name,
                    check
                )
            }
        }
    }
    #[test]
    fn test_get_sig_op_count() {
        struct TestVector<'a> {
            name: &'a str,
            signature_script: &'a [u8],
            expected_sig_ops: u64,
            prev_script_public_key: ScriptPublicKey,
        }
        let script_hash = hex::decode("433ec2ac1ffa1b7b7d027f564529c57197f9ae88").unwrap();
        let prev_script_pubkey_p2sh_script =
            [OpBlake2b, OpData32].iter().copied().chain(script_hash.iter().copied()).chain(once(OpEqual));
        let prev_script_pubkey_p2sh = ScriptPublicKey::new(0, SmallVec::from_iter(prev_script_pubkey_p2sh_script));
        let tests = [
            TestVector {
                name: "scriptSig doesn't parse",
                signature_script: &[OpPushData1, 0x02],
                expected_sig_ops: 0,
                prev_script_public_key: prev_script_pubkey_p2sh.clone(),
            },
            TestVector {
                name: "scriptSig isn't push only",
                signature_script: &[OpTrue, OpDup],
                expected_sig_ops: 0,
                prev_script_public_key: prev_script_pubkey_p2sh.clone(),
            },
            TestVector {
                name: "scriptSig length 0",
                signature_script: &[],
                expected_sig_ops: 0,
                prev_script_public_key: prev_script_pubkey_p2sh.clone(),
            },
            TestVector {
                name: "No script at the end",
                signature_script: &[OpTrue, OpTrue],
                expected_sig_ops: 0,
                prev_script_public_key: prev_script_pubkey_p2sh.clone(),
            }, TestVector {
                name: "pushed script doesn't parse",
                signature_script: &[OpData2, OpPushData1, 0x02],
                expected_sig_ops: 0,
                prev_script_public_key: prev_script_pubkey_p2sh,
            },
            TestVector {
                name: "mainnet multisig transaction 487f94ffa63106f72644068765b9dc629bb63e481210f382667d4a93b69af412",
                signature_script: &hex::decode("41eb577889fa28283709201ef5b056745c6cf0546dd31666cecd41c40a581b256e885d941b86b14d44efacec12d614e7fcabf7b341660f95bab16b71d766ab010501411c0eeef117ca485d34e4bc0cf6d5b578aa250c5d13ebff0882a7e2eeea1f31e8ecb6755696d194b1b0fcb853afab28b61f3f7cec487bd611df7e57252802f535014c875220ab64c7691713a32ea6dfced9155c5c26e8186426f0697af0db7a4b1340f992d12041ae738d66fe3d21105483e5851778ad73c5cddf0819c5e8fd8a589260d967e72065120722c36d3fac19646258481dd3661fa767da151304af514cb30af5cb5692203cd7690ecb67cbbe6cafad00a7c9133da535298ab164549e0cce2658f7b3032754ae").unwrap(),
                prev_script_public_key: ScriptPublicKey::new(
                    0,
                    SmallVec::from_slice(&hex::decode("aa20f38031f61ca23d70844f63a477d07f0b2c2decab907c2e096e548b0e08721c7987").unwrap()),
                ),
                expected_sig_ops: 4,
            },
            TestVector {
                name: "a partially parseable script public key",
                signature_script: &[],
                prev_script_public_key: ScriptPublicKey::new(
                    0,
                    SmallVec::from_slice(&[OpCheckSig,OpCheckSig, OpData1]),
                ),
                expected_sig_ops: 2,
            },
            TestVector {
                name: "p2pk",
                signature_script: &hex::decode("416db0c0ce824a6d076c8e73aae9987416933df768e07760829cb0685dc0a2bbb11e2c0ced0cab806e111a11cbda19784098fd25db176b6a9d7c93e5747674d32301").unwrap(),
                prev_script_public_key: ScriptPublicKey::new(
                    0,
                    SmallVec::from_slice(&hex::decode("208a457ca74ade0492c44c440da1cab5b008d8449150fe2794f0d8f4cce7e8aa27ac").unwrap()),
                ),
                expected_sig_ops: 1,
            },
        ];
        for test in tests {
            assert_eq!(
                get_sig_op_count::<VerifiableTransactionMock>(test.signature_script, &test.prev_script_public_key),
                test.expected_sig_ops,
                "failed for '{}'",
                test.name
            );
        }
    }
    #[test]
    fn test_is_unspendable() {
        struct Test<'a> {
            name: &'a str,
            script_public_key: &'a [u8],
            expected: bool,
        }
        let tests = vec![
            Test { name: "unspendable", script_public_key: &[0x6a, 0x04, 0x74, 0x65, 0x73, 0x74], expected: true },
            Test {
                name: "spendable",
                script_public_key: &[
                    0x76, 0xa9, 0x14, 0x29, 0x95, 0xa0, 0xfe, 0x68, 0x43, 0xfa, 0x9b, 0x95, 0x45, 0x97, 0xf0, 0xdc, 0xa7, 0xa4, 0x4d,
                    0xf6, 0xfa, 0x0b, 0x5c, 0x88, 0xac,
                ],
                expected: false,
            },
        ];
        for test in tests {
            assert_eq!(
                is_unspendable::<VerifiableTransactionMock>(test.script_public_key),
                test.expected,
                "failed for '{}'",
                test.name
            );
        }
    }
}
#[cfg(test)]
mod bitcoind_tests {
    use serde::Deserialize;
    use std::fs::File;
    use std::io::BufReader;
    use std::path::Path;
    use super::*;
    use crate::script_builder::ScriptBuilderError;
    use kaspa_consensus_core::constants::MAX_TX_IN_SEQUENCE_NUM;
    use kaspa_consensus_core::tx::{
        PopulatedTransaction, ScriptPublicKey, Transaction, TransactionId, TransactionOutpoint, TransactionOutput,
    };
    #[derive(PartialEq, Eq, Debug, Clone)]
    enum UnifiedError {
        TxScriptError(TxScriptError),
        ScriptBuilderError(ScriptBuilderError),
    }
    #[derive(PartialEq, Eq, Debug, Clone)]
    struct TestError {
        expected_result: String,
        result: Result<(), UnifiedError>,
    }
    #[derive(Deserialize, Debug, Clone)]
    #[serde(untagged)]
    enum JsonTestRow {
        Test(String, String, String, String),
        TestWithComment(String, String, String, String, String),
        Comment((String,)),
    }
    fn create_spending_transaction(sig_script: Vec<u8>, script_public_key: ScriptPublicKey) -> Transaction {
        let coinbase = Transaction::new(
            1,
            vec![TransactionInput::new(
                TransactionOutpoint::new(TransactionId::default(), 0xffffffffu32),
                vec![0, 0],
                MAX_TX_IN_SEQUENCE_NUM,
                Default::default(),
            )],
            vec![TransactionOutput::new(0, script_public_key)],
            Default::default(),
            Default::default(),
            Default::default(),
            Default::default(),
        );
        Transaction::new(
            1,
            vec![TransactionInput::new(
                TransactionOutpoint::new(coinbase.id(), 0u32),
                sig_script,
                MAX_TX_IN_SEQUENCE_NUM,
                Default::default(),
            )],
            vec![TransactionOutput::new(0, Default::default())],
            Default::default(),
            Default::default(),
            Default::default(),
            Default::default(),
        )
    }
    impl JsonTestRow {
        fn test_row(&self) -> Result<(), TestError> {
            let (sig_script, script_pub_key, expected_result) = match self.clone() {
                JsonTestRow::Test(sig_script, sig_pub_key, _, expected_result) => (sig_script, sig_pub_key, expected_result),
                JsonTestRow::TestWithComment(sig_script, sig_pub_key, _, expected_result, _) => {
                    (sig_script, sig_pub_key, expected_result)
                }
                JsonTestRow::Comment(_) => {
                    return Ok(());
                }
            };
            let result = Self::run_test(sig_script, script_pub_key);
            match Self::result_name(result.clone()).contains(&expected_result.as_str()) {
                true => Ok(()),
                false => Err(TestError { expected_result, result }),
            }
        }
        fn run_test(sig_script: String, script_pub_key: String) -> Result<(), UnifiedError> {
            let script_sig = opcodes::parse_short_form(sig_script).map_err(UnifiedError::ScriptBuilderError)?;
            let script_pub_key =
                ScriptPublicKey::from_vec(0, opcodes::parse_short_form(script_pub_key).map_err(UnifiedError::ScriptBuilderError)?);
            let tx = create_spending_transaction(script_sig, script_pub_key.clone());
            let entry = UtxoEntry::new(0, script_pub_key.clone(), 0, true);
            let populated_tx = PopulatedTransaction::new(&tx, vec![entry]);
            let sig_cache = Cache::new(10_000);
            let mut reused_values = SigHashReusedValues::new();
            let mut vm = TxScriptEngine::from_transaction_input(
                &populated_tx,
                &populated_tx.tx().inputs[0],
                0,
                &populated_tx.entries[0],
                &mut reused_values,
                &sig_cache,
            )
            .map_err(UnifiedError::TxScriptError)?;
            vm.execute().map_err(UnifiedError::TxScriptError)
        }
        fn result_name(result: Result<(), UnifiedError>) -> Vec<&'static str> {
            match result {
                Ok(_) => vec!["OK"],
                Err(ue) => match ue {
                    UnifiedError::TxScriptError(e) => match e {
                        TxScriptError::NumberTooBig(_) => vec!["UNKNOWN_ERROR"],
                        TxScriptError::PubKeyFormat => vec!["PUBKEYFORMAT"],
                        TxScriptError::EvalFalse => vec!["EVAL_FALSE"],
                        TxScriptError::EmptyStack => {
                            vec!["EMPTY_STACK", "EVAL_FALSE", "UNBALANCED_CONDITIONAL", "INVALID_ALTSTACK_OPERATION"]
                        }
                        TxScriptError::NullFail => vec!["NULLFAIL"],
                        TxScriptError::SigLength(_) => vec!["NULLFAIL"],
                        TxScriptError::InvalidSigHashType(_) => vec!["SIG_HASHTYPE"],
                        TxScriptError::SignatureScriptNotPushOnly => vec!["SIG_PUSHONLY"],
                        TxScriptError::CleanStack(_) => vec!["CLEANSTACK"],
                        TxScriptError::OpcodeReserved(_) => vec!["BAD_OPCODE"],
                        TxScriptError::MalformedPush(_, _) => vec!["BAD_OPCODE"],
                        TxScriptError::InvalidOpcode(_) => vec!["BAD_OPCODE"],
                        TxScriptError::ErrUnbalancedConditional => vec!["UNBALANCED_CONDITIONAL"],
                        TxScriptError::InvalidState(s) if s == "condition stack empty" => vec!["UNBALANCED_CONDITIONAL"],
                        TxScriptError::EarlyReturn => vec!["OP_RETURN"],
                        TxScriptError::VerifyError => vec!["VERIFY", "EQUALVERIFY"],
                        TxScriptError::InvalidStackOperation(_, _) => vec!["INVALID_STACK_OPERATION", "INVALID_ALTSTACK_OPERATION"],
                        TxScriptError::InvalidState(s) if s == "pick at an invalid location" => vec!["INVALID_STACK_OPERATION"],
                        TxScriptError::InvalidState(s) if s == "roll at an invalid location" => vec!["INVALID_STACK_OPERATION"],
                        TxScriptError::OpcodeDisabled(_) => vec!["DISABLED_OPCODE"],
                        TxScriptError::ElementTooBig(_, _) => vec!["PUSH_SIZE"],
                        TxScriptError::TooManyOperations(_) => vec!["OP_COUNT"],
                        TxScriptError::StackSizeExceeded(_, _) => vec!["STACK_SIZE"],
                        TxScriptError::InvalidPubKeyCount(_) => vec!["PUBKEY_COUNT"],
                        TxScriptError::InvalidSignatureCount(_) => vec!["SIG_COUNT"],
                        TxScriptError::NotMinimalData(_) => vec!["MINIMALDATA", "UNKNOWN_ERROR"],
                        TxScriptError::UnsatisfiedLockTime(_) => vec!["UNSATISFIED_LOCKTIME"],
                        TxScriptError::InvalidState(s) if s == "expected boolean" => vec!["MINIMALIF"],
                        TxScriptError::ScriptSize(_, _) => vec!["SCRIPT_SIZE"],
                        _ => vec![],
                    },
                    UnifiedError::ScriptBuilderError(e) => match e {
                        ScriptBuilderError::ElementExceedsMaxSize(_) => vec!["PUSH_SIZE"],
                        _ => vec![],
                    },
                },
            }
        }
    }
    #[test]
    fn test_bitcoind_tests() {
        let file = File::open(Path::new(env!("CARGO_MANIFEST_DIR")).join("test-data").join("script_tests.json"))
            .expect("Could not find test file");
        let reader = BufReader::new(file);
        let tests: Vec<JsonTestRow> = serde_json::from_reader(reader).expect("Failed Parsing {:?}");
        let mut had_errors = 0;
        let total_tests = tests.len();
        for row in tests {
            if let Err(error) = row.test_row() {
                println!("Test: {:?} failed: {:?}", row.clone(), error);
                had_errors += 1;
            }
        }
        if had_errors > 0 {
            panic!("{}/{} json tests failed", had_errors, total_tests)
        }
    }
}