use alloy::{
    primitives::{Address, U256},
    providers::DynProvider,
    sol,
};
use async_stream::stream;
use futures::Stream;

sol! {
   #[sol(rpc)]
   contract UniswapV2Factory {
        function getPair(address tokenA, address tokenB) external view returns (address pair);
        function allPairs(uint) external view returns (address pair);
        function allPairsLength() external view returns (uint);
   }
}

pub fn fetch_all_pairs<'a>(
    provider: &'a DynProvider,
    factory_address: Address,
) -> impl Stream<Item = crate::Result<Address>> + 'a {
    stream! {
        let factory = UniswapV2Factory::new(factory_address, provider);
        let fr = &factory;
        let max = match fr.allPairsLength().call().await { Ok(m) => m, Err(e) => { yield Err(crate::error::EthPricesError::from(e)); return; } };

        let mut state = U256::from(0);
        while state < max {

            let pair = match fr.allPairs(U256::from(state)).call().await { Ok(p) => p, Err(e) => { yield Err(crate::error::EthPricesError::from(e)); return; } };

            yield Ok(pair);

            state += U256::from(1);
        }
    }
}

pub async fn fetch_pair(
    provider: &DynProvider,
    factory_address: Address,
    token_from: Address,
    token_to: Address,
) -> crate::Result<Address> {
    let factory = UniswapV2Factory::new(factory_address, provider);
    let fr = &factory;

    let pair = fr.getPair(token_from, token_to).call().await?;
    Ok(pair)
}

#[cfg(test)]
mod tests {
    use alloy::primitives::address;
    use futures::StreamExt;

    use super::*;
    use crate::tests::get_test_provider;

    const FACTORY_ADDRESS: Address = address!("0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f");

    #[tokio::test]
    async fn test_fetch_all_pairs() {
        let provider = get_test_provider().await;
        let pairs = fetch_all_pairs(&provider, FACTORY_ADDRESS);

        let first_pairs = pairs.take(5).map(|x| x.unwrap()).collect::<Vec<_>>().await;

        let dummy: Vec<Address> = vec![
            address!("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc"),
            address!("0x3139ffc91b99aa94da8a2dc13f1fc36f9bdc98ee"),
            address!("0x12ede161c702d1494612d19f05992f43aa6a26fb"),
            address!("0xa478c2975ab1ea89e8196811f51a7b7ade33eb11"),
            address!("0x07f068ca326a469fc1d87d85d448990c8cba7df9"),
        ];

        assert_eq!(first_pairs, dummy)
    }

    #[tokio::test]
    async fn test_fetch_pair() {
        let provider = get_test_provider().await;
        let token_usdc = address!("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48");
        let token_weth = address!("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2");
        let pair = fetch_pair(&provider, FACTORY_ADDRESS, token_usdc, token_weth)
            .await
            .unwrap();

        assert_eq!(pair, address!("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc"));
    }
}
