"""
Safe Addresses. Should be the same for every chain except for the ones with `chainId` protection. Check:
https://github.com/gnosis/safe-deployments/tree/main/src/assets

GnosisSafe V1.3.0: 0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552
GnosisSafe V1.1.1: 0x34CfAC646f301356fAa8B21e94227e3583Fe3F5F
GnosisSafe V1.1.0: 0xaE32496491b53841efb51829d6f886387708F99B
GnosisSafe V1.0.0: 0xb6029EA3B2c51D09a50B53CA8012FeEB05bDa35A

Factories
ProxyFactory V1.3.0: 0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2
ProxyFactory V1.1.0: 0x50e55Af101C777bA7A1d560a774A82eF002ced9F
ProxyFactory V1.0.0: 0x12302fE9c02ff50939BaAaaf415fc226C078613C

FallbackHandler
CompatibilityFallBackHandler V1.3.0: 0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4

Libraries
CreateAndAddModules: 0x1a56aE690ab0818aF5cA349b7D21f1d7e76a3d36
MultiSend: 0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761
"""

import json
import os
import sys
from typing import Any, Dict, Optional

from eth_typing import ChecksumAddress
from hexbytes import HexBytes
from web3 import Web3
from web3.contract import Contract

try:
    from functools import cache
except ImportError:
    from functools import lru_cache

    cache = lru_cache(maxsize=None)


def load_contract_interface(file_name):
    return _load_json_file(_abi_file_path(file_name))


def _abi_file_path(file):
    return os.path.abspath(os.path.join(os.path.dirname(__file__), file))


def _load_json_file(path):
    with open(path) as f:
        return json.load(f)


current_module = sys.modules[__name__]
contracts = {
    "safe_V1_3_0": "GnosisSafe_V1_3_0.json",
    "safe_V1_1_1": "GnosisSafe_V1_1_1.json",
    "safe_V1_0_0": "GnosisSafe_V1_0_0.json",
    "safe_V0_0_1": "GnosisSafe_V0_0_1.json",
    "compatibility_fallback_handler_V1_3_0": "CompatibilityFallbackHandler_V1_3_0.json",
    "erc20": "ERC20.json",
    "erc721": "ERC721.json",
    "erc1155": "ERC1155.json",
    "example_erc20": "ERC20TestToken.json",
    "delegate_constructor_proxy": "DelegateConstructorProxy.json",
    "multi_send": "MultiSend.json",
    "paying_proxy": "PayingProxy.json",
    "proxy_factory": "ProxyFactory_V1_3_0.json",
    "proxy_factory_V1_1_1": "ProxyFactory_V1_1_1.json",
    "proxy_factory_V1_0_0": "ProxyFactory_V1_0_0.json",
    "proxy": "Proxy_V1_1_1.json",
    "uniswap_exchange": "uniswap_exchange.json",
    "uniswap_factory": "uniswap_factory.json",
    "uniswap_v2_factory": "uniswap_v2_factory.json",
    "uniswap_v2_pair": "uniswap_v2_pair.json",
    "uniswap_v2_router": "uniswap_v2_router.json",  # Router02
    "kyber_network_proxy": "kyber_network_proxy.json",
    "cpk_factory": "CPKFactory.json",
}


def generate_contract_fn(contract: Dict[str, Any]):
    """
    Dynamically generate functions to work with the contracts

    :param contract:
    :return:
    """

    def fn(w3: Web3, address: Optional[ChecksumAddress] = None):
        return w3.eth.contract(
            address=address, abi=contract["abi"], bytecode=contract.get("bytecode")
        )

    return fn


# Anotate functions that will be generated later with `setattr` so typing does not complains
def get_safe_contract(w3: Web3, address: Optional[str] = None) -> Contract:
    """
    :param w3:
    :param address:
    :return: Latest available safe contract (v1.3.0)
    """
    return get_safe_V1_3_0_contract(w3, address=address)


def get_safe_V1_3_0_contract(w3: Web3, address: Optional[str] = None) -> Contract:
    pass


def get_safe_V1_1_1_contract(w3: Web3, address: Optional[str] = None) -> Contract:
    pass


def get_safe_V1_0_0_contract(w3: Web3, address: Optional[str] = None) -> Contract:
    pass


def get_safe_V0_0_1_contract(w3: Web3, address: Optional[str] = None) -> Contract:
    pass


def get_compatibility_fallback_handler_V1_3_0_contract(
    w3: Web3, address: Optional[str] = None
) -> Contract:
    pass


def get_erc20_contract(w3: Web3, address: Optional[str] = None) -> Contract:
    pass


def get_erc721_contract(w3: Web3, address: Optional[str] = None) -> Contract:
    pass


def get_erc1155_contract(w3: Web3, address: Optional[str] = None) -> Contract:
    pass


def get_example_erc20_contract(w3: Web3, address: Optional[str] = None) -> Contract:
    pass


def get_delegate_constructor_proxy_contract(
    w3: Web3, address: Optional[str] = None
) -> Contract:
    pass


def get_multi_send_contract(w3: Web3, address: Optional[str] = None) -> Contract:
    pass


def get_paying_proxy_contract(w3: Web3, address: Optional[str] = None) -> Contract:
    pass


def get_proxy_factory_contract(w3: Web3, address: Optional[str] = None) -> Contract:
    pass


def get_proxy_factory_V1_1_1_contract(
    w3: Web3, address: Optional[str] = None
) -> Contract:
    pass


def get_proxy_factory_V1_0_0_contract(
    w3: Web3, address: Optional[str] = None
) -> Contract:
    pass


def get_proxy_contract(w3: Web3, address: Optional[str] = None) -> Contract:
    pass


def get_uniswap_exchange_contract(w3: Web3, address: Optional[str] = None) -> Contract:
    pass


def get_uniswap_factory_contract(w3: Web3, address: Optional[str] = None) -> Contract:
    pass


def get_uniswap_v2_factory_contract(
    w3: Web3, address: Optional[str] = None
) -> Contract:
    pass


def get_uniswap_v2_pair_contract(w3: Web3, address: Optional[str] = None) -> Contract:
    pass


def get_uniswap_v2_router_contract(w3: Web3, address: Optional[str] = None) -> Contract:
    pass


def get_kyber_network_proxy_contract(
    w3: Web3, address: Optional[str] = None
) -> Contract:
    pass


def get_cpk_factory_contract(w3: Web3, address: Optional[str] = None) -> Contract:
    pass


@cache
def get_proxy_1_3_0_deployed_bytecode() -> bytes:
    return HexBytes(load_contract_interface("Proxy_V1_3_0.json")["deployedBytecode"])


def get_proxy_1_1_1_mainnet_deployed_bytecode() -> bytes:
    """
    Somehow it's different from the generated version compiling the contracts
    """
    return HexBytes(
        "0x608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea265627a7a72315820d8a00dc4fe6bf675a9d7416fc2d00bb3433362aa8186b750f76c4027269667ff64736f6c634300050e0032"
    )


@cache
def get_proxy_1_1_1_deployed_bytecode() -> bytes:
    return HexBytes(load_contract_interface("Proxy_V1_1_1.json")["deployedBytecode"])


@cache
def get_proxy_1_0_0_deployed_bytecode() -> bytes:
    return HexBytes(load_contract_interface("Proxy_V1_0_0.json")["deployedBytecode"])


@cache
def get_paying_proxy_deployed_bytecode() -> bytes:
    return HexBytes(load_contract_interface("PayingProxy.json")["deployedBytecode"])


for contract_name, json_contract_filename in contracts.items():
    fn_name = "get_{}_contract".format(contract_name)
    contract_dict = load_contract_interface(json_contract_filename)
    if not contract_dict:
        raise ValueError(f"{contract_name} json cannot be empty")
    setattr(current_module, fn_name, generate_contract_fn(contract_dict))
