use substreams_ethereum::{
    pb::eth::v2::{Call, Log, TransactionTrace},
    Event, Function,
};

use crate::abi;
use tycho_substreams::{
    attributes::{json_serialize_address_list, json_serialize_bigint_list},
    prelude::*,
};

use crate::consts::*;
use substreams::scalar::BigInt;

/// This trait defines some helpers for serializing and deserializing `Vec<BigInt>` which is needed
///  to be able to encode some of the `Attribute`s. This should also be handled by any downstream
///  application.
#[allow(dead_code)]
trait SerializableVecBigInt {
    fn serialize_bytes(&self) -> Vec<u8>;
    fn deserialize_bytes(bytes: &[u8]) -> Vec<BigInt>;
}

impl SerializableVecBigInt for Vec<BigInt> {
    fn serialize_bytes(&self) -> Vec<u8> {
        self.iter()
            .flat_map(|big_int| big_int.to_signed_bytes_be())
            .collect()
    }
    fn deserialize_bytes(bytes: &[u8]) -> Vec<BigInt> {
        bytes
            .chunks_exact(32)
            .map(BigInt::from_signed_bytes_be)
            .collect::<Vec<BigInt>>()
    }
}

/// Converts address bytes into a Vec<u8> containing a leading `0x`.
fn address_to_bytes_with_0x(address: &[u8; 20]) -> Vec<u8> {
    address_to_string_with_0x(address).into_bytes()
}

/// Converts address bytes into a string containing a leading `0x`.
fn address_to_string_with_0x(address: &[u8]) -> String {
    format!("0x{}", hex::encode(address))
}

/// Function that swaps `WETH` addresses for `ETH` address for specific factory types that decide
///  to use `WETH` address even though native `ETH` is stored. This is also extra weird bc ETH
///  doesn't even have a real address, so we use the standard `0xEEEee...`.
fn swap_weth_for_eth(tokens: Vec<Vec<u8>>) -> Vec<Vec<u8>> {
    if tokens.contains(&WETH_ADDRESS.into()) {
        tokens
            .into_iter()
            .map(|token| if token == WETH_ADDRESS { ETH_ADDRESS.into() } else { token })
            .collect::<Vec<_>>()
    } else {
        tokens
    }
}

/// This massive function matches factory address to specific logic to construct
///  `ProtocolComponent`s and their related `EntityChanges` at creation. While, most of the logic is
/// readily replicable, several factories differ  in information density resulting in needing other
/// information sources such as decoding calls  or even making RPC calls to provide extra details.
///
/// Each `ProtocolComponent` contains the following static attributes:
/// - `pool_type`: The type of pool, such as `crypto_pool`, `plain_pool`, `metapool`, etc.
/// - `name`: The name of the pool.
/// - `factory_name`: The name of the factory that created the pool.
/// - `factory`: The address of the factory that created the pool.
///
/// The basic flow of this function is as follows:
/// - Match the factory address
/// - Decode the relevant event from the log
/// - Attempt to decode the corresponding function call (based on the permutation of the ABI)
/// - Optionally make an RPC call to produce further information (see metapools)
/// - Construct the corresponding `ProtocolComponent`
pub fn address_map(
    call_address: &[u8; 20],
    log: &Log,
    call: &Call,
    tx: &TransactionTrace,
) -> Option<(ProtocolComponent, Vec<EntityChanges>)> {
    match *call_address {
        CRYPTO_POOL_FACTORY => {
            let pool_added =
                abi::crypto_pool_factory::events::CryptoPoolDeployed::match_and_decode(log)?;

            let pool_name = abi::crypto_pool_factory::functions::DeployPool::match_and_decode(call)
                .map_or("none".to_string(), |call| call.name);

            let tokens = swap_weth_for_eth(pool_added.coins.into());

            let component_id = &call.return_data[12..];

            let token_implementation = extract_proxy_impl(call, tx, 0).unwrap_or([1u8; 20]);
            let pool_implementation = extract_proxy_impl(call, tx, 1).unwrap_or([1u8; 20]);

            Some((
                ProtocolComponent {
                    id: hex::encode(component_id),
                    tokens: tokens.clone(),
                    contracts: vec![
                        component_id.into(),
                        pool_added.token.clone(),
                        CRYPTO_POOL_FACTORY.into(),
                    ],
                    static_att: vec![
                        Attribute {
                            name: "pool_type".into(),
                            value: "crypto_pool".into(),
                            change: ChangeType::Creation.into(),
                        },
                        Attribute {
                            name: "name".into(),
                            value: pool_name.into(),
                            change: ChangeType::Creation.into(),
                        },
                        Attribute {
                            name: "factory_name".into(),
                            value: "crypto_pool_factory".into(),
                            change: ChangeType::Creation.into(),
                        },
                        Attribute {
                            name: "factory".into(),
                            value: address_to_bytes_with_0x(&CRYPTO_POOL_FACTORY),
                            change: ChangeType::Creation.into(),
                        },
                        Attribute {
                            name: "lp_token".into(),
                            value: pool_added.token,
                            change: ChangeType::Creation.into(),
                        },
                        Attribute {
                            name: "coins".into(),
                            value: json_serialize_address_list(&tokens),
                            change: ChangeType::Creation.into(),
                        },
                    ],
                    change: ChangeType::Creation.into(),
                    protocol_type: Some(ProtocolType {
                        name: "curve_pool".into(),
                        financial_type: FinancialType::Swap.into(),
                        attribute_schema: Vec::new(),
                        implementation_type: ImplementationType::Vm.into(),
                    }),
                },
                vec![EntityChanges {
                    component_id: address_to_string_with_0x(component_id),
                    attributes: vec![
                        Attribute {
                            name: "stateless_contract_addr_0".into(),
                            value: address_to_bytes_with_0x(&pool_implementation),
                            change: ChangeType::Creation.into(),
                        },
                        Attribute {
                            name: "stateless_contract_addr_1".into(),
                            value: address_to_bytes_with_0x(&token_implementation),
                            change: ChangeType::Creation.into(),
                        },
                    ],
                }],
            ))
        }
        META_POOL_FACTORY => {
            if let Some(pool_added) =
                abi::meta_pool_factory::events::PlainPoolDeployed::match_and_decode(log)
            {
                let add_pool =
                    abi::meta_pool_factory::functions::DeployPlainPool1::match_and_decode(call)
                        .map(|add_pool| abi::meta_pool_factory::functions::DeployPlainPool3 {
                            name: add_pool.name,
                            symbol: add_pool.symbol,
                            coins: add_pool.coins,
                            a: add_pool.a,
                            fee: add_pool.fee,
                            asset_type: BigInt::from(0),
                            implementation_idx: BigInt::from(0),
                        })
                        .or_else(|| {
                            abi::meta_pool_factory::functions::DeployPlainPool2::match_and_decode(
                                call,
                            )
                            .map(|add_pool| {
                                abi::meta_pool_factory::functions::DeployPlainPool3 {
                                    name: add_pool.name,
                                    symbol: add_pool.symbol,
                                    coins: add_pool.coins,
                                    a: add_pool.a,
                                    fee: add_pool.fee,
                                    asset_type: add_pool.asset_type,
                                    implementation_idx: BigInt::from(0),
                                }
                            })
                        })
                        .or_else(|| {
                            abi::meta_pool_factory::functions::DeployPlainPool3::match_and_decode(
                                call,
                            )
                        })?;

                // The return data of several of these calls contain the actual component id
                let component_id = &call.return_data[12..];
                let tokens: Vec<_> = pool_added
                    .coins
                    .into_iter()
                    .filter(|token| *token != [0; 20])
                    .collect();

                let pool_implementation = extract_proxy_impl(call, tx, 0).unwrap_or([1u8; 20]);
                Some((
                    ProtocolComponent {
                        id: hex::encode(component_id),
                        tokens: tokens.clone(),
                        contracts: vec![component_id.into()],
                        static_att: vec![
                            Attribute {
                                name: "pool_type".into(),
                                value: "plain_pool".into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "name".into(),
                                value: add_pool.name.into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "factory_name".into(),
                                value: "meta_pool_factory".into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "factory".into(),
                                value: address_to_bytes_with_0x(&META_POOL_FACTORY),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "coins".into(),
                                value: json_serialize_address_list(&tokens),
                                change: ChangeType::Creation.into(),
                            },
                        ],
                        change: ChangeType::Creation.into(),
                        protocol_type: Some(ProtocolType {
                            name: "curve_pool".into(),
                            financial_type: FinancialType::Swap.into(),
                            attribute_schema: Vec::new(),
                            implementation_type: ImplementationType::Vm.into(),
                        }),
                    },
                    vec![EntityChanges {
                        component_id: address_to_string_with_0x(component_id),
                        attributes: vec![Attribute {
                            name: "stateless_contract_addr_0".into(),
                            value: address_to_bytes_with_0x(&pool_implementation),
                            change: ChangeType::Creation.into(),
                        }],
                    }],
                ))
            }
            // else if let Some(pool_added) =
            //     abi::meta_pool_factory::events::MetaPoolDeployed::match_and_decode(log)
            // {
            //     let add_pool =
            //         abi::meta_pool_factory::functions::DeployMetapool1::match_and_decode(call)
            //             .map(|add_pool| abi::meta_pool_factory::functions::DeployMetapool2 {
            //                 base_pool: add_pool.base_pool,
            //                 name: add_pool.name,
            //                 symbol: add_pool.symbol,
            //                 coin: add_pool.coin,
            //                 a: add_pool.a,
            //                 fee: add_pool.fee,
            //                 implementation_idx: BigInt::from(0),
            //             })
            //             .or_else(|| {
            //                 abi::meta_pool_factory::functions::DeployMetapool2::match_and_decode(
            //                     call,
            //                 )
            //             })?;

            //     let component_id = &call.return_data[12..];

            //     // The `add_pool.base_pool` may only refer to the contract of the base pool and
            // not     //  the token itself. This means we **have** to make an RPC call
            // to the     //  `meta_registry` in order to get the real LP token address.
            //     let get_lp_token =
            //         abi::meta_registry::functions::GetLpToken1 { pool: add_pool.base_pool.clone()
            // };     let lp_token = get_lp_token.call(META_REGISTRY.to_vec())?;

            //     let pool_implementation = extract_proxy_impl(call, tx, 0).unwrap_or([1u8; 20]);

            //     Some((
            //         ProtocolComponent {
            //             id: hex::encode(component_id),
            //             tx: Some(Transaction {
            //                 to: tx.to.clone(),
            //                 from: tx.from.clone(),
            //                 hash: tx.hash.clone(),
            //                 index: tx.index.into(),
            //             }),
            //             tokens: vec![pool_added.coin, lp_token],
            //             contracts: vec![component_id.into(), add_pool.base_pool.clone()],
            //             static_att: vec![
            //                 Attribute {
            //                     name: "pool_type".into(),
            //                     value: "metapool".into(),
            //                     change: ChangeType::Creation.into(),
            //                 },
            //                 Attribute {
            //                     name: "name".into(),
            //                     value: add_pool.name.into(),
            //                     change: ChangeType::Creation.into(),
            //                 },
            //                 Attribute {
            //                     name: "factory_name".into(),
            //                     value: "meta_pool_factory".into(),
            //                     change: ChangeType::Creation.into(),
            //                 },
            //                 Attribute {
            //                     name: "factory".into(),
            //                     value: address_to_bytes_with_0x(&META_POOL_FACTORY),
            //                     change: ChangeType::Creation.into(),
            //                 },
            //                 Attribute {
            //                     name: "base_pool".into(),
            //                     value: address_to_bytes_with_0x(
            //                         &add_pool.base_pool.try_into().unwrap(),
            //                     ),
            //                     change: ChangeType::Creation.into(),
            //                 },
            //                 TODO:ADD COINS
            //             ],
            //             change: ChangeType::Creation.into(),
            //             protocol_type: Some(ProtocolType {
            //                 name: "curve_pool".into(),
            //                 financial_type: FinancialType::Swap.into(),
            //                 attribute_schema: Vec::new(),
            //                 implementation_type: ImplementationType::Vm.into(),
            //             }),
            //         },
            //         vec![EntityChanges {
            //             component_id: address_to_string_with_0x(component_id),
            //             attributes: vec![Attribute {
            //                 name: "stateless_contract_addr_0".into(),
            //                 value: address_to_bytes_with_0x(&pool_implementation),
            //                 change: ChangeType::Creation.into(),
            //             }],
            //         }],
            //     ))
            // }
            else {
                None
            }
        }
        // META_POOL_FACTORY_OLD => {
        //     if let Some(pool_added) =
        //         abi::meta_pool_factory::events::MetaPoolDeployed::match_and_decode(log)
        //     {
        //         let add_pool =
        //             abi::meta_pool_factory::functions::DeployMetapool1::match_and_decode(call)
        //                 .map(|add_pool| abi::meta_pool_factory::functions::DeployMetapool2 {
        //                     base_pool: add_pool.base_pool,
        //                     name: add_pool.name,
        //                     symbol: add_pool.symbol,
        //                     coin: add_pool.coin,
        //                     a: add_pool.a,
        //                     fee: add_pool.fee,
        //                     implementation_idx: BigInt::from(0),
        //                 })
        //                 .or_else(|| {
        //                     abi::meta_pool_factory::functions::DeployMetapool2::match_and_decode(
        //                         call,
        //                     )
        //                 })?;

        //         let pool_implementation = extract_proxy_impl(call, tx, 0).unwrap_or([1u8; 20]);

        //         let component_id = &call.return_data[12..];
        //         let lp_token = get_token_from_pool(&pool_added.base_pool);

        //         Some((
        //             ProtocolComponent {
        //                 id: hex::encode(component_id),
        //                 tx: Some(Transaction {
        //                     to: tx.to.clone(),
        //                     from: tx.from.clone(),
        //                     hash: tx.hash.clone(),
        //                     index: tx.index.into(),
        //                 }),
        //                 tokens: vec![pool_added.coin, lp_token],
        //                 contracts: vec![component_id.into()],
        //                 static_att: vec![
        //                     Attribute {
        //                         name: "pool_type".into(),
        //                         value: "metapool".into(),
        //                         change: ChangeType::Creation.into(),
        //                     },
        //                     Attribute {
        //                         name: "name".into(),
        //                         value: add_pool.name.into(),
        //                         change: ChangeType::Creation.into(),
        //                     },
        //                     Attribute {
        //                         name: "factory_name".into(),
        //                         value: "meta_pool_factory_old".into(),
        //                         change: ChangeType::Creation.into(),
        //                     },
        //                     Attribute {
        //                         name: "factory".into(),
        //                         value: address_to_bytes_with_0x(&META_POOL_FACTORY_OLD),
        //                         change: ChangeType::Creation.into(),
        //                     },
        //                     Attribute {
        //                         name: "base_pool".into(),
        //                         value: address_to_bytes_with_0x(
        //                             &add_pool.base_pool.try_into().unwrap(),
        //                         ),
        //                         change: ChangeType::Creation.into(),
        //                     },
        //                 TODO:ADD COINS
        //                 ],
        //                 change: ChangeType::Creation.into(),
        //                 protocol_type: Some(ProtocolType {
        //                     name: "curve_pool".into(),
        //                     financial_type: FinancialType::Swap.into(),
        //                     attribute_schema: Vec::new(),
        //                     implementation_type: ImplementationType::Vm.into(),
        //                 }),
        //             },
        //             vec![EntityChanges {
        //                 component_id: address_to_string_with_0x(component_id),
        //                 attributes: vec![Attribute {
        //                     name: "stateless_contract_addr_0".into(),
        //                     value: address_to_bytes_with_0x(&pool_implementation),
        //                     change: ChangeType::Creation.into(),
        //                 }],
        //             }],
        //         ))
        //     } else {
        //         None
        //     }
        // }
        CRYPTO_SWAP_NG_FACTORY => {
            if let Some(pool_added) =
                abi::crypto_swap_ng_factory::events::PlainPoolDeployed::match_and_decode(log)
            {
                let add_pool =
                    abi::crypto_swap_ng_factory::functions::DeployPlainPool::match_and_decode(
                        call,
                    )?;
                let component_id = &call.return_data[12..];
                Some((
                    ProtocolComponent {
                        id: hex::encode(component_id),
                        tokens: pool_added.coins.clone(),
                        contracts: vec![component_id.into(), CRYPTO_SWAP_NG_FACTORY.into()],
                        static_att: vec![
                            Attribute {
                                name: "pool_type".into(),
                                value: "plain_pool".into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "name".into(),
                                value: add_pool.name.into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "factory_name".into(),
                                value: "crypto_swap_ng_factory".into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "factory".into(),
                                value: address_to_bytes_with_0x(&CRYPTO_SWAP_NG_FACTORY),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "asset_types".into(),
                                value: json_serialize_bigint_list(&add_pool.asset_types),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "coins".into(),
                                value: json_serialize_address_list(&pool_added.coins),
                                change: ChangeType::Creation.into(),
                            },
                        ],
                        change: ChangeType::Creation.into(),
                        protocol_type: Some(ProtocolType {
                            name: "curve_pool".into(),
                            financial_type: FinancialType::Swap.into(),
                            attribute_schema: Vec::new(),
                            implementation_type: ImplementationType::Vm.into(),
                        }),
                    },
                    vec![EntityChanges {
                        component_id: address_to_string_with_0x(component_id),
                        attributes: vec![Attribute {
                            name: "stateless_contract_addr_0".into(),
                            // Call views_implementation() on CRYPTO_SWAP_NG_FACTORY
                            value: format!(
                                "call:0x{}:views_implementation()",
                                hex::encode(CRYPTO_SWAP_NG_FACTORY)
                            )
                            .into(),
                            change: ChangeType::Creation.into(),
                        }],
                    }],
                ))
            } else if let Some(pool_added) =
                abi::crypto_swap_ng_factory::events::MetaPoolDeployed::match_and_decode(log)
            {
                let add_pool =
                    abi::crypto_swap_ng_factory::functions::DeployMetapool::match_and_decode(call)?;
                let component_id = &call.return_data[12..];
                let lp_token = get_token_from_pool(&pool_added.base_pool);

                Some((
                    ProtocolComponent {
                        id: hex::encode(component_id),
                        tokens: vec![pool_added.coin.clone(), lp_token.clone()],
                        contracts: vec![
                            component_id.into(),
                            CRYPTO_SWAP_NG_FACTORY.into(),
                            pool_added.base_pool.clone(),
                        ],
                        static_att: vec![
                            Attribute {
                                name: "pool_type".into(),
                                value: "metapool".into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "name".into(),
                                value: add_pool.name.into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "factory_name".into(),
                                value: "crypto_swap_ng_factory".into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "factory".into(),
                                value: address_to_bytes_with_0x(&CRYPTO_SWAP_NG_FACTORY),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "base_pool".into(),
                                value: address_to_bytes_with_0x(
                                    &pool_added.base_pool.try_into().unwrap(),
                                ),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "asset_type".into(),
                                value: add_pool.asset_type.to_signed_bytes_be(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "coins".into(),
                                value: json_serialize_address_list(&[pool_added.coin, lp_token]),
                                change: ChangeType::Creation.into(),
                            },
                        ],
                        change: ChangeType::Creation.into(),
                        protocol_type: Some(ProtocolType {
                            name: "curve_pool".into(),
                            financial_type: FinancialType::Swap.into(),
                            attribute_schema: Vec::new(),
                            implementation_type: ImplementationType::Vm.into(),
                        }),
                    },
                    vec![EntityChanges {
                        component_id: address_to_string_with_0x(component_id),
                        attributes: vec![
                            Attribute {
                                name: "stateless_contract_addr_0".into(),
                                // Call views_implementation() on CRYPTO_SWAP_NG_FACTORY
                                value: format!(
                                    "call:0x{}:views_implementation()",
                                    hex::encode(CRYPTO_SWAP_NG_FACTORY)
                                )
                                .into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "stateless_contract_addr_1".into(),
                                // Call math_implementation() on CRYPTO_SWAP_NG_FACTORY
                                value: format!(
                                    "call:0x{}:math_implementation()",
                                    hex::encode(CRYPTO_SWAP_NG_FACTORY)
                                )
                                .into(),
                                change: ChangeType::Creation.into(),
                            },
                        ],
                    }],
                ))
            } else {
                None
            }
        }
        TRICRYPTO_FACTORY => {
            if let Some(pool_added) =
                abi::tricrypto_factory::events::TricryptoPoolDeployed::match_and_decode(log)
            {
                let tokens = swap_weth_for_eth(pool_added.coins.into());
                let id = hex::encode(&pool_added.pool);

                Some((
                    ProtocolComponent {
                        id: id.clone(),
                        tokens: tokens.clone(),
                        contracts: vec![pool_added.pool, TRICRYPTO_FACTORY.into()],
                        static_att: vec![
                            Attribute {
                                name: "pool_type".into(),
                                value: "tricrypto".into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "name".into(),
                                value: pool_added.name.into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "factory_name".into(),
                                value: "tricrypto_factory".into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "factory".into(),
                                value: address_to_bytes_with_0x(&TRICRYPTO_FACTORY),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "coins".into(),
                                value: json_serialize_address_list(&tokens),
                                change: ChangeType::Creation.into(),
                            },
                        ],
                        change: ChangeType::Creation.into(),
                        protocol_type: Some(ProtocolType {
                            name: "curve_pool".into(),
                            financial_type: FinancialType::Swap.into(),
                            attribute_schema: Vec::new(),
                            implementation_type: ImplementationType::Vm.into(),
                        }),
                    },
                    vec![EntityChanges {
                        component_id: format!("0x{id}"),
                        attributes: vec![
                            Attribute {
                                name: "stateless_contract_addr_0".into(),
                                // Call views_implementation() on TRICRYPTO_FACTORY
                                value: format!(
                                    "call:0x{}:views_implementation()",
                                    hex::encode(TRICRYPTO_FACTORY)
                                )
                                .into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "stateless_contract_addr_1".into(),
                                // Call math_implementation() on TRICRYPTO_FACTORY
                                value: format!(
                                    "call:0x{}:math_implementation()",
                                    hex::encode(TRICRYPTO_FACTORY)
                                )
                                .into(),
                                change: ChangeType::Creation.into(),
                            },
                        ],
                    }],
                ))
            } else {
                None
            }
        }
        STABLESWAP_FACTORY => {
            if let Some(pool_added) =
                abi::stableswap_factory::events::PlainPoolDeployed::match_and_decode(log)
            {
                let add_pool = if let Some(pool) =
                    abi::stableswap_factory::functions::DeployPlainPool1::match_and_decode(call)
                {
                    abi::stableswap_factory::functions::DeployPlainPool3 {
                        name: pool.name,
                        symbol: pool.symbol,
                        coins: pool.coins,
                        a: pool.a,
                        fee: pool.fee,
                        asset_type: BigInt::from(0),
                        implementation_idx: BigInt::from(0),
                    }
                } else if let Some(pool) =
                    abi::stableswap_factory::functions::DeployPlainPool2::match_and_decode(call)
                {
                    abi::stableswap_factory::functions::DeployPlainPool3 {
                        name: pool.name,
                        symbol: pool.symbol,
                        coins: pool.coins,
                        a: pool.a,
                        fee: pool.fee,
                        asset_type: BigInt::from(0),
                        implementation_idx: BigInt::from(0),
                    }
                } else if let Some(pool) =
                    abi::stableswap_factory::functions::DeployPlainPool3::match_and_decode(call)
                {
                    pool
                } else {
                    return None;
                };
                let component_id = &call.return_data[12..];

                let tokens: Vec<_> = pool_added
                    .coins
                    .into_iter()
                    .filter(|token| *token != [0; 20])
                    .collect();

                let pool_implementation = extract_proxy_impl(call, tx, 0).unwrap_or([1u8; 20]);

                Some((
                    ProtocolComponent {
                        id: hex::encode(component_id),
                        tokens: tokens.clone(),
                        contracts: vec![component_id.into()],
                        static_att: vec![
                            Attribute {
                                name: "pool_type".into(),
                                value: "plain_pool".into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "name".into(),
                                value: add_pool.name.into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "factory_name".into(),
                                value: "stable_swap_factory".into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "factory".into(),
                                value: address_to_bytes_with_0x(&STABLESWAP_FACTORY),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "coins".into(),
                                value: json_serialize_address_list(&tokens),
                                change: ChangeType::Creation.into(),
                            },
                        ],
                        change: ChangeType::Creation.into(),
                        protocol_type: Some(ProtocolType {
                            name: "curve_pool".into(),
                            financial_type: FinancialType::Swap.into(),
                            attribute_schema: Vec::new(),
                            implementation_type: ImplementationType::Vm.into(),
                        }),
                    },
                    vec![EntityChanges {
                        component_id: address_to_string_with_0x(component_id),
                        attributes: vec![Attribute {
                            name: "stateless_contract_addr_0".into(),
                            value: address_to_bytes_with_0x(&pool_implementation),
                            change: ChangeType::Creation.into(),
                        }],
                    }],
                ))
            }
            // else if let Some(pool_added) =
            //     abi::stableswap_factory::events::MetaPoolDeployed::match_and_decode(log)
            // {
            //     let add_pool = if let Some(pool) =
            //         abi::stableswap_factory::functions::DeployMetapool1::match_and_decode(call)
            //     {
            //         abi::stableswap_factory::functions::DeployMetapool2 {
            //             base_pool: pool.base_pool,
            //             name: pool.name,
            //             symbol: pool.symbol,
            //             coin: pool.coin,
            //             a: pool.a,
            //             fee: pool.fee,
            //             implementation_idx: BigInt::from(0),
            //         }
            //     } else if let Some(pool) =
            //         abi::stableswap_factory::functions::DeployMetapool2::match_and_decode(call)
            //     {
            //         pool
            //     } else {
            //         return None;
            //     };
            //     let component_id = &call.return_data[12..];

            //     let get_lp_token =
            //         abi::meta_registry::functions::GetLpToken1 { pool: add_pool.base_pool.clone()
            // };     let lp_token = get_lp_token.call(META_REGISTRY.to_vec())?;

            //     Some((
            //         ProtocolComponent {
            //             id: hex::encode(component_id),
            //             tx: Some(Transaction {
            //                 to: tx.to.clone(),
            //                 from: tx.from.clone(),
            //                 hash: tx.hash.clone(),
            //                 index: tx.index.into(),
            //             }),
            //             tokens: vec![pool_added.coin, lp_token],
            //             contracts: vec![component_id.into()],
            //             static_att: vec![
            //                 Attribute {
            //                     name: "pool_type".into(),
            //                     value: "metapool".into(),
            //                     change: ChangeType::Creation.into(),
            //                 },
            //                 Attribute {
            //                     name: "name".into(),
            //                     value: add_pool.name.into(),
            //                     change: ChangeType::Creation.into(),
            //                 },
            //                 Attribute {
            //                     name: "factory_name".into(),
            //                     value: "stable_swap_factory".into(),
            //                     change: ChangeType::Creation.into(),
            //                 },
            //                 Attribute {
            //                     name: "factory".into(),
            //                     value: address_to_bytes_with_0x(&STABLESWAP_FACTORY),
            //                     change: ChangeType::Creation.into(),
            //                 },
            //                 Attribute {
            //                     name: "base_pool".into(),
            //                     value: address_to_bytes_with_0x(
            //                         &pool_added.base_pool.try_into().unwrap(),
            //                     ),
            //                     change: ChangeType::Creation.into(),
            //                 },
            //             ],
            //             change: ChangeType::Creation.into(),
            //             protocol_type: Some(ProtocolType {
            //                 name: "curve_pool".into(),
            //                 financial_type: FinancialType::Swap.into(),
            //                 attribute_schema: Vec::new(),
            //                 implementation_type: ImplementationType::Vm.into(),
            //             }),
            //         },
            //         vec![],
            //     ))
            // }
            else {
                None
            }
        }
        TWOCRYPTO_FACTORY => {
            if let Some(pool_added) =
                abi::twocrypto_factory::events::TwocryptoPoolDeployed::match_and_decode(log)
            {
                let id = hex::encode(&pool_added.pool);

                Some((
                    ProtocolComponent {
                        id: id.clone(),
                        tokens: pool_added.coins.clone().into(),
                        contracts: vec![pool_added.pool, TWOCRYPTO_FACTORY.into()],
                        static_att: vec![
                            Attribute {
                                name: "pool_type".into(),
                                value: "twocrypto".into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "name".into(),
                                value: pool_added.name.into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "factory_name".into(),
                                value: "twocrypto_factory".into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "factory".into(),
                                value: address_to_bytes_with_0x(&TWOCRYPTO_FACTORY),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "coins".into(),
                                value: json_serialize_address_list(&pool_added.coins),
                                change: ChangeType::Creation.into(),
                            },
                        ],
                        change: ChangeType::Creation.into(),
                        protocol_type: Some(ProtocolType {
                            name: "curve_pool".into(),
                            financial_type: FinancialType::Swap.into(),
                            attribute_schema: Vec::new(),
                            implementation_type: ImplementationType::Vm.into(),
                        }),
                    },
                    vec![EntityChanges {
                        component_id: format!("0x{id}"),
                        attributes: vec![
                            Attribute {
                                name: "stateless_contract_addr_0".into(),
                                // Call views_implementation() on TWOCRYPTO_FACTORY
                                value: format!(
                                    "call:0x{}:views_implementation()",
                                    hex::encode(TWOCRYPTO_FACTORY)
                                )
                                .into(),
                                change: ChangeType::Creation.into(),
                            },
                            Attribute {
                                name: "stateless_contract_addr_1".into(),
                                value: address_to_bytes_with_0x(
                                    &pool_added
                                        .math
                                        .try_into()
                                        .unwrap_or([1u8; 20]), // Unexpected issue marker
                                ),
                                change: ChangeType::Creation.into(),
                            },
                        ],
                    }],
                ))
            } else {
                None
            }
        }
        _ => None,
    }
}

/// This function makes 3 attempts to confirm / get the LP token address from a pool address.
///
/// 1. We attempt to see if the pool address is a token address itself by calling an ERC 20 func.
///  - Some pools may not be the token themselves
/// 2. Then, we try to ping the `META_REGISTRY` address to see if it has a record of the pool.
///  - Older pools might have been created before the `META_REGISTRY` was created and therefore
///    would have registered much later
/// 3. Finally, we have a hardcoded map of pool address -> token address for some pools.
///
/// If all else fails, we force an `unwrap` to trigger a `panic` so that we can resolve this by
///  adding onto our map of `pool` -> `token` addresses.
fn get_token_from_pool(pool: &Vec<u8>) -> Vec<u8> {
    abi::erc20::functions::Name {}
        .call(pool.clone())
        .and(Some(pool.clone()))
        .or_else(|| {
            abi::meta_registry::functions::GetLpToken1 { pool: pool.clone() }
                .call(META_REGISTRY.to_vec())
        })
        .or_else(|| {
            match hex::encode(pool).as_str() {
                // Curve.fi DAI/USDC/USDT (3Crv)
                "bebc44782c7db0a1a60cb6fe97d0b483032ff1c7" => {
                    hex::decode("6c3F90f043a72FA612cbac8115EE7e52BDe6E490").ok()
                }
                // Curve.fi renBTC/wBTC/sBTC (crvRenWSBTC)
                "7fc77b5c7614e1533320ea6ddc2eb61fa00a9714" => {
                    hex::decode("075b1bb99792c9e1041ba13afef80c91a1e70fb3").ok()
                }
                // Placeholder if we can't find the token. It will help us to detect these missing
                // token easily with a SQL query.
                _ => hex::decode("1111111111111111111111111111111111111111").ok(),
            }
        })
        .unwrap()
}

fn extract_eip1167_target_from_code(code: &[u8]) -> [u8; 20] {
    let mut target = [0u8; 20];

    // Depending on the Vyper version, they use different implementations of EIP1167.
    // We use the first 10 bytes of the code to make a clear distinction.
    match code.get(0..10) {
        Some([54, 61, 61, 55, 61, 61, 61, 54, 61, 115]) => target.copy_from_slice(&code[10..30]),
        Some([54, 96, 0, 96, 0, 55, 97, 16, 0, 96]) => target.copy_from_slice(&code[15..35]),
        _ => target = [1u8; 20], // Placeholder for unexpected values
    }

    target
}

fn extract_proxy_impl(call: &Call, tx: &TransactionTrace, index: usize) -> Option<[u8; 20]> {
    let code_change = tx
        .calls
        .iter()
        .filter(|c| !c.code_changes.is_empty() && c.parent_index == call.index)
        .nth(index)?
        .code_changes
        .first()?;
    Some(extract_eip1167_target_from_code(&code_change.new_code))
}
