import hashlib
from typing import List
from neo3.network import payloads
from neo3.core import types, serialization, to_script_hash
from neo3 import vm, contracts, blockchain, storage


def contract_hash(sender: types.UInt160, checksum: int, name: str) -> types.UInt160:
    sb = vm.ScriptBuilder()
    sb.emit(vm.OpCode.ABORT)
    sb.emit_push(sender.to_array())
    sb.emit_push(checksum)
    sb.emit_push(name)
    return to_script_hash(sb.to_array())


def syscall_name_to_int(name: str) -> int:
    return int.from_bytes(hashlib.sha256(name.encode()).digest()[:4], 'little', signed=False)


def test_engine(has_container=False, has_snapshot=False, default_script=True, call_flags=contracts.CallFlags.ALL):
    tx = payloads.Transaction._serializable_init()

    # this little hack basically nullifies the singleton behaviour and ensures we create
    # a new instance every time we call it. This in turn gives us a clean backend/snapshot
    blockchain.Blockchain.__it__ = None

    snapshot = blockchain.Blockchain(store_genesis_block=True).currentSnapshot
    if has_container and has_snapshot:
        engine = contracts.ApplicationEngine(contracts.TriggerType.APPLICATION, tx, snapshot, 0, test_mode=True)
    elif has_container:
        engine = contracts.ApplicationEngine(contracts.TriggerType.APPLICATION, tx, None, 0, test_mode=True)
    elif has_snapshot:
        engine = contracts.ApplicationEngine(contracts.TriggerType.APPLICATION, None, snapshot, 0, test_mode=True)
    else:
        engine = contracts.ApplicationEngine(contracts.TriggerType.APPLICATION, None, None, 0, test_mode=True)

    if default_script:
        engine.load_script_with_callflags(vm.Script(b'\x40'), call_flags)  # OpCode::RET
    return engine


def test_tx(with_block_height=1, signers: List[types.UInt160]=None) -> payloads.Transaction:
    if signers is None:
        new_signers = [payloads.Signer(types.UInt160.from_string("f782c7fbb2eef6afe629b96c0d53fb525eda64ce"), payloads.WitnessScope.GLOBAL)]
    else:
        new_signers = list(map(lambda v: payloads.Signer(v, payloads.WitnessScope.GLOBAL), signers))

    tx = payloads.Transaction(version=0,
                              nonce=123,
                              system_fee=456,
                              network_fee=789,
                              valid_until_block=with_block_height + 1,
                              attributes=[],
                              signers=new_signers,
                              script=b'\x01',
                              witnesses=[])
    tx.block_height = with_block_height
    return tx


def test_block(with_index=1) -> payloads.Block:
    tx = test_tx(with_index)
    header1 = payloads.Header(
        version=0,
        prev_hash=types.UInt256.from_string("f782c7fbb2eef6afe629b96c0d53fb525eda64ce5345057caf975ac3c2b9ae0a"),
        timestamp=123,
        nonce=0,
        index=with_index,
        primary_index=0,
        next_consensus=types.UInt160.from_string("d7678dd97c000be3f33e9362e673101bac4ca654"),
        witness=payloads.Witness(invocation_script=b'', verification_script=b'\x55')
    )
    block1 = payloads.Block(header1, transactions=[tx])
    block1.rebuild_merkle_root()
    return block1


class TestIVerifiable(payloads.IVerifiable):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.script_hashes = [types.UInt160.zero()]

    def serialize(self, writer: serialization.BinaryWriter) -> None:
        pass

    def deserialize(self, reader: serialization.BinaryReader) -> None:
        pass

    def __len__(self):
        pass

    def serialize_unsigned(self, writer: serialization.BinaryWriter) -> None:
        pass

    def deserialize_unsigned(self, reader: serialization.BinaryReader) -> None:
        pass

    def get_script_hashes_for_verifying(self, snapshot: storage.Snapshot) -> List[types.UInt160]:
        return self.script_hashes
