from typing import List, Optional

from mev_inspect.schemas.blocks import Block
from mev_inspect.schemas.traces import Trace, TraceType

weth_address = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"

cache_directory = "./cache"


def is_stablecoin_address(address):
    # to look for stablecoin inflow/outflows
    stablecoin_addresses = [
        "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",  # USDC
        "0xdac17f958d2ee523a2206206994597c13d831ec7",  # USDT
        "0x6b175474e89094c44da98b954eedeac495271d0f",  # DAI
        "0x0000000000085d4780b73119b644ae5ecd22b376",  # TUSD
        "0x4fabb145d64652a948d72533023f6e7a623c7c53",  # BUSD
        "0x8e870d67f660d95d5be530380d0ec0bd388289e1",  # PAX
        "0x956F47F50A910163D8BF957Cf5846D573E7f87CA",  # FEI
        "0x853d955aCEf822Db058eb8505911ED77F175b99e",  # FRAX
        "0xBC6DA0FE9aD5f3b0d58160288917AA56653660E9",  # alUSD
        "0x57Ab1ec28D129707052df4dF418D58a2D46d5f51",  # sUSD
        "0x5f98805A4E8be255a32880FDeC7F6728C6568bA0",  # lUSD
        "0x674C6Ad92Fd080e4004b2312b45f796a192D27a0",  # USDN
    ]
    return address in stablecoin_addresses


def is_known_router_address(address):
    # to exclude known router addresses from token flow analysis
    known_router_addresses = [
        "0x3D71d79C224998E608d03C5Ec9B405E7a38505F0",  # keeper dao, whitelists extraction
        "0x11111254369792b2Ca5d084aB5eEA397cA8fa48B",  # 1inch v1 router
        "0x111111125434b319222cdbf8c261674adb56f3ae",  # 1inch v2 router
        "0x11111112542d85b3ef69ae05771c2dccff4faa26",  # 1inch v3 router
        "0xa356867fdcea8e71aeaf87805808803806231fdc",  # DODO
        "0xdef1c0ded9bec7f1a1670819833240f027b25eff",  # 0x proxy
        "0x90f765f63e7dc5ae97d6c576bf693fb6af41c129",  # Set Trade
        "0x7113dd99c79aff93d54cfa4b2885576535a132de",  # Totle exchange
        "0x9509665d015bfe3c77aa5ad6ca20c8afa1d98989",  # Paraswap
        "0x86969d29F5fd327E1009bA66072BE22DB6017cC6",  # Paraswap v2
        "0xf90e98f3d8dce44632e5020abf2e122e0f99dfab",  # Paraswap v3
        "0x57805e5a227937bac2b0fdacaa30413ddac6b8e1",  # Furucombo
        "0x17e8ca1b4798b97602895f63206afcd1fc90ca5f",  # Furucombo proxy
        "0x881d40237659c251811cec9c364ef91dc08d300c",  # Metamask swap
        "0x745daa146934b27e3f0b6bff1a6e36b9b90fb131",  # DEX.ag
        "0xb2be281e8b11b47fec825973fc8bb95332022a54",  # Zerion SDK
        "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",  # UniswapV2Router02
        "0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F",  # SushiswapV2Router02
        "0xE592427A0AEce92De3Edee1F18E0157C05861564",  # Uniswap v3 router
        "0x3E66B66Fd1d0b02fDa6C811Da9E0547970DB2f21",  # Balance exchange proxy
        "0x1bD435F3C054b6e901B7b108a0ab7617C808677b",  # Paraswap v4
        "0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F",  # SNX proxy synth issuer
    ]
    return address in known_router_addresses


# we're interested in the to address to run token flow on it as well
def get_tx_to_address(tx_hash, block) -> Optional[str]:
    for receipt in block.receipts:
        if receipt.transaction_hash == tx_hash:
            return receipt.to

    return None


def get_tx_proxies(tx_traces: List[Trace], to_address: Optional[str]):
    proxies = []

    for trace in tx_traces:
        if (
            trace.type == TraceType.call
            and trace.action["callType"] == "delegatecall"
            and trace.action["from"] == to_address
        ):
            proxies.append(trace.action["to"])

    return proxies


def get_net_gas_used(tx_hash, block):
    gas_used = 0
    for trace in block.traces:
        if trace.transaction_hash == tx_hash:
            gas_used += int(trace.result["gasUsed"], 16)

    return gas_used


def get_ether_flows(tx_traces, addresses_to_check):
    eth_inflow = 0
    eth_outflow = 0

    for trace in tx_traces:
        if trace.type == TraceType.call:
            value = int(
                trace.action["value"], 16
            )  # converting from 0x prefix to decimal
            # ETH_GET
            if (
                trace.action["callType"] != "delegatecall"
                and trace.action["from"] != weth_address
                and value > 0
                and trace.action["to"] in addresses_to_check
            ):
                eth_inflow = eth_inflow + value

            # ETH_GIVE
            if (
                trace.action["callType"] != "delegatecall"
                and trace.action["to"] != weth_address
                and value > 0
                and trace.action["from"] in addresses_to_check
            ):
                eth_outflow = eth_outflow + value

            if trace.action["to"] == weth_address:
                # WETH_GET1 & WETH_GET2 (to account for both 'transfer' and 'transferFrom' methods)
                # WETH_GIVE1 & WETH_GIVE2

                # transfer(address to,uint256 value) with args
                if len(trace.action["input"]) == 138:
                    if trace.action["input"][2:10] == "a9059cbb":
                        transfer_to = "0x" + trace.action["input"][34:74]
                        transfer_value = int("0x" + trace.action["input"][74:138], 16)
                        if transfer_to in addresses_to_check:
                            eth_inflow = eth_inflow + transfer_value
                        elif trace.action["from"] in addresses_to_check:
                            eth_outflow = eth_outflow + transfer_value

                # transferFrom(address from,address to,uint256 value )
                if len(trace.action["input"]) == 202:
                    if trace.action["input"][2:10] == "23b872dd":
                        transfer_from = "0x" + trace.action["input"][34:74]
                        transfer_to = "0x" + trace.action["input"][98:138]
                        transfer_value = int("0x" + trace.action["input"][138:202], 16)
                        if transfer_to in addresses_to_check:
                            eth_inflow = eth_inflow + transfer_value
                        elif transfer_from in addresses_to_check:
                            eth_outflow = eth_outflow + transfer_value

        if trace.type == TraceType.suicide:
            if trace.action["refundAddress"] in addresses_to_check:
                refund_value = int("0x" + trace.action["balance"], 16)
                eth_inflow = eth_inflow + refund_value

    return [eth_inflow, eth_outflow]


def get_dollar_flows(tx_traces, addresses_to_check):
    dollar_inflow = 0
    dollar_outflow = 0
    for trace in tx_traces:
        if trace.type == TraceType.call and is_stablecoin_address(trace.action["to"]):
            _ = int(trace.action["value"], 16)  # converting from 0x prefix to decimal

            # USD_GET1 & USD_GET2 (to account for both 'transfer' and 'transferFrom' methods)
            # USD_GIVE1 & USD_GIVE2

            # transfer(address to,uint256 value) with args
            if len(trace.action["input"]) == 138:
                if trace.action["input"][2:10] == "a9059cbb":
                    transfer_to = "0x" + trace.action["input"][34:74]
                    transfer_value = int("0x" + trace.action["input"][74:138], 16)
                    if transfer_to in addresses_to_check:
                        dollar_inflow = dollar_inflow + transfer_value
                    elif trace.action["from"] in addresses_to_check:
                        dollar_outflow = dollar_outflow + transfer_value

            # transferFrom(address from,address to,uint256 value )
            if len(trace.action["input"]) == 202:
                if trace.action["input"][2:10] == "23b872dd":
                    transfer_from = "0x" + trace.action["input"][34:74]
                    transfer_to = "0x" + trace.action["input"][98:138]
                    transfer_value = int("0x" + trace.action["input"][138:202], 16)
                    if transfer_to in addresses_to_check:
                        dollar_inflow = dollar_inflow + transfer_value
                    elif transfer_from in addresses_to_check:
                        dollar_outflow = dollar_outflow + transfer_value
    return [dollar_inflow, dollar_outflow]


def run_tokenflow(tx_hash: str, block: Block):
    tx_traces = block.get_filtered_traces(tx_hash)
    to_address = get_tx_to_address(tx_hash, block)

    if to_address is None:
        raise ValueError("No to address found")

    addresses_to_check = []

    # check for proxies, add them to addresses to check
    proxies = get_tx_proxies(tx_traces, to_address)

    for proxy in proxies:
        addresses_to_check.append(proxy.lower())

    # check if the 'to' field is a known aggregator/router
    # if not, add to relevant addresses to run TF on
    if not is_known_router_address(to_address):
        addresses_to_check.append(
            to_address.lower()
        )  # traces need lowercase addresses to match

    ether_flows = get_ether_flows(tx_traces, addresses_to_check)
    dollar_flows = get_dollar_flows(tx_traces, addresses_to_check)
    # print(addresses_to_check)
    # print('net eth flow', ether_flows[0] - ether_flows[1])
    # print('net dollar flow', dollar_flows )
    return {"ether_flows": ether_flows, "dollar_flows": dollar_flows}


# note: not the gas set by user, only gas consumed upon execution
# def get_gas_used_by_tx(tx_hash):
#     # tx_receipt = w3.eth.getTransactionReceipt(tx_hash)
#     return tx_receipt["gasUsed"]


# tx_traces = get_tx_traces('0x4121ce805d33e952b2e6103a5024f70c118432fd0370128d6d7845f9b2987922', 11930296)
# print(tx_traces)

# print(type(known_router_addresses))
# print(is_stablecoin_address("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"))

# run_tokenflow("0x4121ce805d33e952b2e6103a5024f70c118432fd0370128d6d7845f9b2987922", 11930296)

# delegate call test
# run_tokenflow("0x9007b339c81de366cd53539edc15c86ffc87542c65f374c0d4d1f8823a3ccf60", 12051659)

# stable flow test
# res  = run_tokenflow("0x496836e0bd1520388e36c79d587a31d4b3306e4f25352164178ca0667c7f9c29", 11935012)
# print(res)

# complex arb test
# res = run_tokenflow("0x5ab21bfba50ad3993528c2828c63e311aafe93b40ee934790e545e150cb6ca73", 11931272)
# print(res)

# get_gas_used_by_tx("0x4121ce805d33e952b2e6103a5024f70c118432fd0370128d6d7845f9b2987922")
