import re
import json
from pathlib import Path

import pytest
from deepdiff import DeepDiff
from web3.contract import Contract

from slither import Slither
from slither.tools.read_storage import SlitherReadStorage, RpcInfo

TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data"


def get_source_file(file_path) -> str:
    with open(file_path, encoding="utf8") as f:
        source = f.read()

    return source


def deploy_contract(w3, ganache, contract_bin, contract_abi) -> Contract:
    """Deploy contract to the local ganache network"""
    signed_txn = w3.eth.account.sign_transaction(
        {
            "nonce": w3.eth.get_transaction_count(ganache.eth_address),
            "maxFeePerGas": 20000000000,
            "maxPriorityFeePerGas": 1,
            "gas": 15000000,
            "to": b"",
            "data": "0x" + contract_bin,
            "chainId": 1,
        },
        ganache.eth_privkey,
    )
    tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction)
    address = w3.eth.get_transaction_receipt(tx_hash)["contractAddress"]
    contract = w3.eth.contract(address, abi=contract_abi)
    return contract


@pytest.mark.parametrize(
    "test_contract, storage_file",
    [("StorageLayout", "storage_layout"), ("UnstructuredStorageLayout", "unstructured_storage")],
)
@pytest.mark.usefixtures("web3", "ganache")
def test_read_storage(test_contract, storage_file, web3, ganache, solc_binary_path) -> None:
    solc_path = solc_binary_path(version="0.8.10")

    assert web3.is_connected()
    bin_path = Path(TEST_DATA_DIR, f"{test_contract}.bin").as_posix()
    abi_path = Path(TEST_DATA_DIR, f"{test_contract}.abi").as_posix()
    bytecode = get_source_file(bin_path)
    abi = get_source_file(abi_path)
    contract = deploy_contract(web3, ganache, bytecode, abi)
    contract.functions.store().transact({"from": ganache.eth_address})
    address = contract.address

    sl = Slither(Path(TEST_DATA_DIR, f"{test_contract}.sol").as_posix(), solc=solc_path)
    contracts = sl.contracts

    rpc_info: RpcInfo = RpcInfo(ganache.provider)
    srs = SlitherReadStorage(contracts, 100, rpc_info)
    srs.unstructured = True
    srs.storage_address = address
    srs.get_all_storage_variables()
    srs.get_storage_layout()
    srs.walk_slot_info(srs.get_slot_values)
    actual_file = Path(TEST_DATA_DIR, "storage_layout.json").as_posix()
    with open(actual_file, "w", encoding="utf-8") as file:
        slot_infos_json = srs.to_json()
        json.dump(slot_infos_json, file, indent=4)

    expected_file = Path(TEST_DATA_DIR, f"TEST_{storage_file}.json").as_posix()

    with open(expected_file, encoding="utf8") as f:
        expected = json.load(f)
    with open(actual_file, encoding="utf8") as f:
        actual = json.load(f)

    diff = DeepDiff(expected, actual, ignore_order=True, verbose_level=2, view="tree")
    if diff:
        for change in diff.get("values_changed", []):
            path_list = re.findall(r"\['(.*?)'\]", change.path())
            path = "_".join(path_list)
            with open(f"{path}_expected.txt", "w", encoding="utf8") as f:
                f.write(str(change.t1))
            with open(f"{path}_actual.txt", "w", encoding="utf8") as f:
                f.write(str(change.t2))

    assert not diff
