//! ERC721 obligations module

pub mod barter_utils;
pub mod escrow;
pub mod payment;
pub mod util;

use alloy::primitives::Address;
use alloy::signers::local::PrivateKeySigner;
use serde::{Deserialize, Serialize};

use crate::addresses::BASE_SEPOLIA_ADDRESSES;
use crate::contracts;
use crate::extensions::{AlkahestExtension, ContractModule};
use crate::impl_abi_conversions;
use crate::impl_token_bundle_payment_obligation;
use crate::types::{ApprovalPurpose, Erc721Data, ProviderContext, SharedWalletProvider};

// --- ABI conversions for ERC721 obligation types ---
impl_abi_conversions!(contracts::obligations::ERC721PaymentObligation::ObligationData);
impl_abi_conversions!(contracts::obligations::escrow::non_tierable::ERC721EscrowObligation::ObligationData);
impl_abi_conversions!(contracts::obligations::escrow::tierable::ERC721EscrowObligation::ObligationData);

// --- TokenBundle conversions for ERC721 barter utils ---
impl_token_bundle_payment_obligation!(contracts::utils::erc721::TokenBundlePaymentObligation::ObligationData);

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Erc721Addresses {
    pub eas: Address,
    pub barter_utils: Address,
    pub escrow_obligation_nontierable: Address,
    pub escrow_obligation_tierable: Address,
    pub payment_obligation: Address,
}

impl Default for Erc721Addresses {
    fn default() -> Self {
        BASE_SEPOLIA_ADDRESSES.erc721_addresses
    }
}

/// Available contracts in the ERC721 module
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Erc721Contract {
    Eas,
    BarterUtils,
    EscrowObligation,
    PaymentObligation,
}

#[derive(Clone)]
pub struct Erc721Module {
    pub(crate) signer: PrivateKeySigner,
    pub(crate) wallet_provider: SharedWalletProvider,
    pub addresses: Erc721Addresses,
}

impl ContractModule for Erc721Module {
    type Contract = Erc721Contract;

    fn address(&self, contract: Self::Contract) -> Address {
        match contract {
            Erc721Contract::Eas => self.addresses.eas,
            Erc721Contract::BarterUtils => self.addresses.barter_utils,
            Erc721Contract::EscrowObligation => self.addresses.escrow_obligation_nontierable,
            Erc721Contract::PaymentObligation => self.addresses.payment_obligation,
        }
    }
}

impl Erc721Module {
    pub fn new(
        signer: PrivateKeySigner,
        wallet_provider: SharedWalletProvider,
        addresses: Option<Erc721Addresses>,
    ) -> eyre::Result<Self> {
        Ok(Erc721Module {
            signer,
            wallet_provider,
            addresses: addresses.unwrap_or_default(),
        })
    }

    /// Access escrow API
    pub fn escrow(&self) -> escrow::Escrow<'_> {
        escrow::Escrow::new(self)
    }

    /// Access payment API
    pub fn payment(&self) -> payment::Payment<'_> {
        payment::Payment::new(self)
    }

    /// Access barter utilities API
    pub fn barter(&self) -> barter_utils::BarterUtils<'_> {
        barter_utils::BarterUtils::new(self)
    }

    /// Access utility API (approvals)
    ///
    /// # Example
    /// ```rust,ignore
    /// client.erc721().util().approve(&token, ApprovalPurpose::Payment).await?;
    /// client.erc721().util().approve_all(token_contract, ApprovalPurpose::Escrow).await?;
    /// ```
    pub fn util(&self) -> util::Util<'_> {
        util::Util::new(self)
    }

    /// Approves a specific token for trading.
    pub async fn approve(
        &self,
        token: &Erc721Data,
        purpose: ApprovalPurpose,
    ) -> eyre::Result<alloy::rpc::types::TransactionReceipt> {
        self.util().approve(token, purpose).await
    }

    /// Approves all tokens from a contract for trading.
    pub async fn approve_all(
        &self,
        token_contract: Address,
        purpose: ApprovalPurpose,
    ) -> eyre::Result<alloy::rpc::types::TransactionReceipt> {
        self.util().approve_all(token_contract, purpose).await
    }

    /// Revokes approval for all tokens from a contract.
    pub async fn revoke_all(
        &self,
        token_contract: Address,
        purpose: ApprovalPurpose,
    ) -> eyre::Result<alloy::rpc::types::TransactionReceipt> {
        self.util().revoke_all(token_contract, purpose).await
    }
}

impl AlkahestExtension for Erc721Module {
    type Config = Erc721Addresses;

    async fn init(
        signer: PrivateKeySigner,
        providers: ProviderContext,
        config: Option<Self::Config>,
    ) -> eyre::Result<Self> {
        Self::new(signer, providers.wallet.clone(), config)
    }
}

#[cfg(test)]
mod tests {
    use std::time::{SystemTime, UNIX_EPOCH};

    use alloy::{
        primitives::{Bytes, FixedBytes, U256},
        providers::ext::AnvilApi as _,
        sol_types::SolValue as _,
    };

    use crate::{
        DefaultAlkahestClient,
        extensions::{HasErc20, HasErc721, HasErc1155, HasTokenBundle},
        fixtures::{MockERC20Permit, MockERC721, MockERC1155},
        types::{
            ApprovalPurpose, ArbiterData, Erc20Data, Erc721Data, Erc1155Data, TokenBundleData,
        },
        utils::setup_test_environment,
    };

    #[tokio::test]
    async fn test_decode_escrow_obligation() -> eyre::Result<()> {
        // test setup
        let test = setup_test_environment().await?;

        // Create sample obligation data
        let token_address = test.mock_addresses.erc721_a;
        let id: U256 = U256::from(1);
        let arbiter = test.addresses.erc721_addresses.payment_obligation;
        let demand = Bytes::from(vec![1, 2, 3, 4]); // sample demand data

        let escrow_data = crate::contracts::obligations::escrow::non_tierable::ERC721EscrowObligation::ObligationData {
            token: token_address,
            tokenId: id,
            arbiter,
            demand: demand.clone(),
        };

        // Encode the data
        let encoded = escrow_data.abi_encode();

        // Decode the data using TryFrom<Bytes>
        let decoded: crate::contracts::obligations::escrow::non_tierable::ERC721EscrowObligation::ObligationData =
            alloy::primitives::Bytes::from(encoded).try_into()?;

        // Verify decoded data
        assert_eq!(decoded.token, token_address, "Token address should match");
        assert_eq!(decoded.tokenId, id, "ID should match");
        assert_eq!(decoded.arbiter, arbiter, "Arbiter should match");
        assert_eq!(decoded.demand, demand, "Demand should match");

        Ok(())
    }

    #[tokio::test]
    async fn test_decode_payment_obligation() -> eyre::Result<()> {
        // test setup
        let test = setup_test_environment().await?;

        // Create sample obligation data
        let token_address = test.mock_addresses.erc721_a;
        let id: U256 = U256::from(1);
        let payee = test.alice.address();

        let payment_data = crate::contracts::obligations::ERC721PaymentObligation::ObligationData {
            token: token_address,
            tokenId: id,
            payee,
        };

        // Encode the data
        let encoded = payment_data.abi_encode();

        // Decode the data using TryFrom<Bytes>
        let decoded: crate::contracts::obligations::ERC721PaymentObligation::ObligationData =
            alloy::primitives::Bytes::from(encoded).try_into()?;

        // Verify decoded data
        assert_eq!(decoded.token, token_address, "Token address should match");
        assert_eq!(decoded.tokenId, id, "ID should match");
        assert_eq!(decoded.payee, payee, "Payee should match");

        Ok(())
    }

    #[tokio::test]
    async fn test_approve() -> eyre::Result<()> {
        // test setup
        let test = setup_test_environment().await?;

        // mint an ERC721 token to alice
        let mock_erc721_a = MockERC721::new(test.mock_addresses.erc721_a, &test.god_provider);
        mock_erc721_a
            .mint(test.alice.address())
            .send()
            .await?
            .get_receipt()
            .await?;

        let token = Erc721Data {
            address: test.mock_addresses.erc721_a,
            id: U256::from(1),
        };

        // Test approve for payment
        let _receipt = test
            .alice_client
            .erc721()
            .approve(&token, ApprovalPurpose::Payment)
            .await?;

        // Verify approval for payment obligation
        let payment_approved = mock_erc721_a.getApproved(U256::from(1)).call().await?;

        assert_eq!(
            payment_approved,
            test.addresses.erc721_addresses.clone().payment_obligation,
            "Payment approval should be set correctly"
        );

        // Test approve for escrow
        let _receipt = test
            .alice_client
            .erc721()
            .approve(&token, ApprovalPurpose::Escrow)
            .await?;

        // Verify approval for escrow obligation
        let escrow_approved = mock_erc721_a.getApproved(U256::from(1)).call().await?;

        assert_eq!(
            escrow_approved, test.addresses.erc721_addresses.escrow_obligation_nontierable,
            "Escrow approval should be set correctly"
        );

        Ok(())
    }

    #[tokio::test]
    async fn test_approve_all() -> eyre::Result<()> {
        // test setup
        let test = setup_test_environment().await?;

        // mint ERC721 tokens to alice
        let mock_erc721_a = MockERC721::new(test.mock_addresses.erc721_a, &test.god_provider);
        mock_erc721_a
            .mint(test.alice.address())
            .send()
            .await?
            .get_receipt()
            .await?;

        mock_erc721_a
            .mint(test.alice.address())
            .send()
            .await?
            .get_receipt()
            .await?;

        // Test approve_all for payment
        let _receipt = test
            .alice_client
            .erc721()
            .approve_all(test.mock_addresses.erc721_a, ApprovalPurpose::Payment)
            .await?;

        // Verify approval for payment obligation
        let payment_approved = mock_erc721_a
            .isApprovedForAll(
                test.alice.address(),
                test.addresses.erc721_addresses.clone().payment_obligation,
            )
            .call()
            .await?;

        assert!(
            payment_approved,
            "Payment approval for all should be set correctly"
        );

        // Test approve_all for escrow
        let _receipt = test
            .alice_client
            .erc721()
            .approve_all(test.mock_addresses.erc721_a, ApprovalPurpose::Escrow)
            .await?;

        // Verify approval for escrow obligation
        let escrow_approved = mock_erc721_a
            .isApprovedForAll(
                test.alice.address(),
                test.addresses.erc721_addresses.escrow_obligation_nontierable,
            )
            .call()
            .await?;

        assert!(
            escrow_approved,
            "Escrow approval for all should be set correctly"
        );

        Ok(())
    }

    #[tokio::test]
    async fn test_revoke_all() -> eyre::Result<()> {
        // test setup
        let test = setup_test_environment().await?;

        // mint ERC721 tokens to alice
        let mock_erc721_a = MockERC721::new(test.mock_addresses.erc721_a, &test.god_provider);
        mock_erc721_a
            .mint(test.alice.address())
            .send()
            .await?
            .get_receipt()
            .await?;

        // First approve all
        test.alice_client
            .erc721()
            .approve_all(test.mock_addresses.erc721_a, ApprovalPurpose::Payment)
            .await?;

        // Then revoke all
        let _receipt = test
            .alice_client
            .erc721()
            .revoke_all(test.mock_addresses.erc721_a, ApprovalPurpose::Payment)
            .await?;

        // Verify revocation
        let payment_approved = mock_erc721_a
            .isApprovedForAll(
                test.alice.address(),
                test.addresses.erc721_addresses.clone().payment_obligation,
            )
            .call()
            .await?;

        assert!(!payment_approved, "Payment approval should be revoked");

        Ok(())
    }

    #[tokio::test]
    async fn test_escrow_create() -> eyre::Result<()> {
        // test setup
        let test = setup_test_environment().await?;

        // mint an ERC721 token to alice
        let mock_erc721_a = MockERC721::new(test.mock_addresses.erc721_a, &test.god_provider);
        mock_erc721_a
            .mint(test.alice.address())
            .send()
            .await?
            .get_receipt()
            .await?;

        let price = Erc721Data {
            address: test.mock_addresses.erc721_a,
            id: U256::from(1),
        };

        // Create custom arbiter data
        let arbiter = test.addresses.erc721_addresses.clone().payment_obligation;
        let demand = Bytes::from(b"custom demand data");
        let item = ArbiterData { arbiter, demand };

        // approve token for escrow
        test.alice_client
            .erc721()
            .approve(&price, ApprovalPurpose::Escrow)
            .await?;

        // alice creates escrow with custom demand
        let receipt = test
            .alice_client
            .erc721()
            .escrow()
            .non_tierable()
            .create(&price, &item, 0)
            .await?;

        // Verify escrow happened
        let owner = mock_erc721_a.ownerOf(U256::from(1)).call().await?;

        // token in escrow
        assert_eq!(
            owner, test.addresses.erc721_addresses.escrow_obligation_nontierable,
            "Token should be owned by escrow contract"
        );

        // escrow obligation made
        let attested_event = DefaultAlkahestClient::get_attested_event(receipt)?;
        assert_ne!(attested_event.uid, FixedBytes::<32>::default());

        Ok(())
    }

    #[tokio::test]
    async fn test_payment_pay() -> eyre::Result<()> {
        // test setup
        let test = setup_test_environment().await?;

        // mint an ERC721 token to alice
        let mock_erc721_a = MockERC721::new(test.mock_addresses.erc721_a, &test.god_provider);
        mock_erc721_a
            .mint(test.alice.address())
            .send()
            .await?
            .get_receipt()
            .await?;

        let price = Erc721Data {
            address: test.mock_addresses.erc721_a,
            id: U256::from(1),
        };

        // approve token for payment
        test.alice_client
            .erc721()
            .approve(&price, ApprovalPurpose::Payment)
            .await?;

        // alice makes direct payment to bob
        let receipt = test
            .alice_client
            .erc721()
            .payment()
            .pay(&price, test.bob.address())
            .await?;

        // Verify payment happened
        let owner = mock_erc721_a.ownerOf(U256::from(1)).call().await?;

        // token paid to bob
        assert_eq!(owner, test.bob.address(), "Token should be owned by Bob");

        // payment obligation made
        let attested_event = DefaultAlkahestClient::get_attested_event(receipt)?;
        assert_ne!(attested_event.uid, FixedBytes::<32>::default());

        Ok(())
    }

    #[tokio::test]
    async fn test_barter_buy_erc721_for_erc721() -> eyre::Result<()> {
        // test setup
        let test = setup_test_environment().await?;

        // mint an ERC721 token to alice
        let mock_erc721_a = MockERC721::new(test.mock_addresses.erc721_a, &test.god_provider);
        mock_erc721_a
            .mint(test.alice.address())
            .send()
            .await?
            .get_receipt()
            .await?;

        // begin test
        let bid = Erc721Data {
            address: test.mock_addresses.erc721_a,
            id: U256::from(1),
        };
        let ask = Erc721Data {
            address: test.mock_addresses.erc721_b,
            id: U256::from(2),
        };

        // alice approves token for barter
        test.alice_client
            .erc721()
            .approve(&bid, ApprovalPurpose::BarterUtils)
            .await?;

        // alice makes escrow
        let receipt = test
            .alice_client
            .erc721()
            .barter()
            .buy_erc721_for_erc721(&bid, &ask, 0)
            .await?;

        // verify escrow
        let owner = mock_erc721_a.ownerOf(U256::from(1)).call().await?;

        assert_eq!(
            owner, test.addresses.erc721_addresses.escrow_obligation_nontierable,
            "Token should be in escrow"
        );

        // escrow obligation made
        let attested_event = DefaultAlkahestClient::get_attested_event(receipt)?;
        assert_ne!(attested_event.uid, FixedBytes::<32>::default());

        Ok(())
    }

    #[tokio::test]
    async fn test_barter_pay_erc721_for_erc721() -> eyre::Result<()> {
        // test setup
        let test = setup_test_environment().await?;

        // mint ERC721 tokens to alice and bob
        let mock_erc721_a = MockERC721::new(test.mock_addresses.erc721_a, &test.god_provider);
        mock_erc721_a
            .mint(test.alice.address())
            .send()
            .await?
            .get_receipt()
            .await?;

        let mock_erc721_b = MockERC721::new(test.mock_addresses.erc721_b, &test.god_provider);
        mock_erc721_b
            .mint(test.bob.address())
            .send()
            .await?
            .get_receipt()
            .await?;

        // begin test
        let bid = Erc721Data {
            address: test.mock_addresses.erc721_a,
            id: U256::from(1),
        };
        let ask = Erc721Data {
            address: test.mock_addresses.erc721_b,
            id: U256::from(1),
        };

        // alice approves token for barter and creates buy attestation
        test.alice_client
            .erc721()
            .approve(&bid, ApprovalPurpose::BarterUtils)
            .await?;

        let buy_receipt = test
            .alice_client
            .erc721()
            .barter()
            .buy_erc721_for_erc721(&bid, &ask, 0)
            .await?;

        let buy_attestation = DefaultAlkahestClient::get_attested_event(buy_receipt)?.uid;

        // bob approves token for barter
        test.bob_client
            .erc721()
            .approve(&ask, ApprovalPurpose::BarterUtils)
            .await?;

        // bob fulfills the buy attestation
        let _sell_receipt = test
            .bob_client
            .erc721()
            .barter()
            .pay_erc721_for_erc721(buy_attestation)
            .await?;

        // verify token transfers
        let alice_token_b_owner = mock_erc721_b.ownerOf(U256::from(1)).call().await?;
        let bob_token_a_owner = mock_erc721_a.ownerOf(U256::from(1)).call().await?;

        // both sides received the tokens
        assert_eq!(
            alice_token_b_owner,
            test.alice.address(),
            "Alice should have received token B"
        );
        assert_eq!(
            bob_token_a_owner,
            test.bob.address(),
            "Bob should have received token A"
        );

        Ok(())
    }

    #[tokio::test]
    async fn test_escrow_reclaim_expired() -> eyre::Result<()> {
        // test setup
        let test = setup_test_environment().await?;

        // mint an ERC721 token to alice
        let mock_erc721_a = MockERC721::new(test.mock_addresses.erc721_a, &test.god_provider);
        mock_erc721_a
            .mint(test.alice.address())
            .send()
            .await?
            .get_receipt()
            .await?;

        // begin test
        let bid = Erc721Data {
            address: test.mock_addresses.erc721_a,
            id: U256::from(1),
        };
        let ask = Erc721Data {
            address: test.mock_addresses.erc721_b,
            id: U256::from(2),
        };

        // alice approves token for barter
        test.alice_client
            .erc721()
            .approve(&bid, ApprovalPurpose::BarterUtils)
            .await?;

        // alice makes escrow with a short expiration
        let expiration = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() + 10;
        let receipt = test
            .alice_client
            .erc721()
            .barter()
            .buy_erc721_for_erc721(&bid, &ask, expiration as u64 + 1)
            .await?;

        let buy_attestation = DefaultAlkahestClient::get_attested_event(receipt)?.uid;

        // Wait for expiration
        test.god_provider.anvil_increase_time(20).await?;

        // alice collects expired funds
        let _collect_receipt = test
            .alice_client
            .erc721()
            .escrow()
            .non_tierable()
            .reclaim_expired(buy_attestation)
            .await?;

        // verify token returned to alice
        let owner = mock_erc721_a.ownerOf(U256::from(1)).call().await?;

        assert_eq!(
            owner,
            test.alice.address(),
            "Token should be returned to Alice"
        );

        Ok(())
    }

    #[tokio::test]
    async fn test_barter_buy_erc20_with_erc721() -> eyre::Result<()> {
        // test setup
        let test = setup_test_environment().await?;

        // mint an ERC721 token to alice
        let mock_erc721_a = MockERC721::new(test.mock_addresses.erc721_a, &test.god_provider);
        mock_erc721_a
            .mint(test.alice.address())
            .send()
            .await?
            .get_receipt()
            .await?;

        // Create exchange information
        let bid = Erc721Data {
            address: test.mock_addresses.erc721_a,
            id: U256::from(1),
        };
        let ask = Erc20Data {
            address: test.mock_addresses.erc20_a,
            value: U256::from(100),
        };

        // alice approves token for barter
        test.alice_client
            .erc721()
            .approve(&bid, ApprovalPurpose::BarterUtils)
            .await?;

        // alice creates purchase offer
        let receipt = test
            .alice_client
            .erc721()
            .barter()
            .buy_erc20_with_erc721(&bid, &ask, 0)
            .await?;

        // Verify escrow happened
        let owner = mock_erc721_a.ownerOf(U256::from(1)).call().await?;

        assert_eq!(
            owner, test.addresses.erc721_addresses.escrow_obligation_nontierable,
            "Token should be in escrow"
        );

        // escrow obligation made
        let attested_event = DefaultAlkahestClient::get_attested_event(receipt)?;
        assert_ne!(attested_event.uid, FixedBytes::<32>::default());

        Ok(())
    }

    #[tokio::test]
    async fn test_barter_buy_erc1155_with_erc721() -> eyre::Result<()> {
        // test setup
        let test = setup_test_environment().await?;

        // mint an ERC721 token to alice
        let mock_erc721_a = MockERC721::new(test.mock_addresses.erc721_a, &test.god_provider);
        mock_erc721_a
            .mint(test.alice.address())
            .send()
            .await?
            .get_receipt()
            .await?;

        // Create exchange information
        let bid = Erc721Data {
            address: test.mock_addresses.erc721_a,
            id: U256::from(1),
        };
        let ask = Erc1155Data {
            address: test.mock_addresses.erc1155_a,
            id: U256::from(1),
            value: U256::from(10),
        };

        // alice approves token for barter
        test.alice_client
            .erc721()
            .approve(&bid, ApprovalPurpose::BarterUtils)
            .await?;

        // alice creates purchase offer
        let receipt = test
            .alice_client
            .erc721()
            .barter()
            .buy_erc1155_with_erc721(&bid, &ask, 0)
            .await?;

        // Verify escrow happened
        let owner = mock_erc721_a.ownerOf(U256::from(1)).call().await?;

        assert_eq!(
            owner, test.addresses.erc721_addresses.escrow_obligation_nontierable,
            "Token should be in escrow"
        );

        // escrow obligation made
        let attested_event = DefaultAlkahestClient::get_attested_event(receipt)?;
        assert_ne!(attested_event.uid, FixedBytes::<32>::default());

        Ok(())
    }

    #[tokio::test]
    async fn test_barter_buy_bundle_with_erc721() -> eyre::Result<()> {
        // test setup
        let test = setup_test_environment().await?;

        // mint an ERC721 token to alice
        let mock_erc721_a = MockERC721::new(test.mock_addresses.erc721_a, &test.god_provider);
        mock_erc721_a
            .mint(test.alice.address())
            .send()
            .await?
            .get_receipt()
            .await?;

        // Create exchange information
        let bid = Erc721Data {
            address: test.mock_addresses.erc721_a,
            id: U256::from(1),
        };

        // Create bundle data
        let bundle = TokenBundleData {
            native_amount: U256::ZERO,
            erc20s: vec![Erc20Data {
                address: test.mock_addresses.erc20_b,
                value: U256::from(20),
            }],
            erc721s: vec![Erc721Data {
                address: test.mock_addresses.erc721_b,
                id: U256::from(2),
            }],
            erc1155s: vec![Erc1155Data {
                address: test.mock_addresses.erc1155_a,
                id: U256::from(1),
                value: U256::from(5),
            }],
        };

        // alice approves token for barter
        test.alice_client
            .erc721()
            .approve(&bid, ApprovalPurpose::BarterUtils)
            .await?;

        // alice creates purchase offer
        let receipt = test
            .alice_client
            .erc721()
            .barter()
            .buy_bundle_with_erc721(&bid, &bundle, 0)
            .await?;

        // Verify escrow happened
        let owner = mock_erc721_a.ownerOf(U256::from(1)).call().await?;

        assert_eq!(
            owner, test.addresses.erc721_addresses.escrow_obligation_nontierable,
            "Token should be in escrow"
        );

        // escrow obligation made
        let attested_event = DefaultAlkahestClient::get_attested_event(receipt)?;
        assert_ne!(attested_event.uid, FixedBytes::<32>::default());

        Ok(())
    }

    #[tokio::test]
    async fn test_barter_pay_erc721_for_erc20() -> eyre::Result<()> {
        // test setup
        let test = setup_test_environment().await?;

        // mint an ERC721 token to alice (she will fulfill with this)
        let mock_erc721_a = MockERC721::new(test.mock_addresses.erc721_a, &test.god_provider);
        mock_erc721_a
            .mint(test.alice.address())
            .send()
            .await?
            .get_receipt()
            .await?;

        // give bob some ERC20 tokens for escrow
        let mock_erc20_a = MockERC20Permit::new(test.mock_addresses.erc20_a, &test.god_provider);
        mock_erc20_a
            .transfer(test.bob.address(), U256::from(100))
            .send()
            .await?
            .get_receipt()
            .await?;

        // begin test
        let bid = Erc20Data {
            // bob's bid
            address: test.mock_addresses.erc20_a,
            value: U256::from(100),
        };
        let ask = Erc721Data {
            // bob asks for alice's ERC721
            address: test.mock_addresses.erc721_a,
            id: U256::from(1),
        };

        // bob approves tokens for barter and creates buy attestation
        test.bob_client
            .erc20()
            .approve(&bid, ApprovalPurpose::BarterUtils)
            .await?;

        let buy_receipt = test
            .bob_client
            .erc20()
            .barter()
            .buy_erc721_for_erc20(&bid, &ask, 0)
            .await?;

        let buy_attestation = DefaultAlkahestClient::get_attested_event(buy_receipt)?.uid;

        // alice approves her ERC721 token for barter
        test.alice_client
            .erc721()
            .approve(&ask, ApprovalPurpose::BarterUtils)
            .await?;

        // alice fulfills bob's buy attestation with her ERC721
        let _sell_receipt = test
            .alice_client
            .erc721()
            .barter()
            .pay_erc721_for_erc20(buy_attestation)
            .await?;

        // verify token transfers
        let alice_token_a_balance = mock_erc20_a.balanceOf(test.alice.address()).call().await?;

        let bob_token_owner = mock_erc721_a.ownerOf(U256::from(1)).call().await?;

        // both sides received the tokens
        assert_eq!(
            alice_token_a_balance,
            U256::from(100),
            "Alice should have received ERC20 tokens"
        );
        assert_eq!(
            bob_token_owner,
            test.bob.address(),
            "Bob should have received the ERC721 token"
        );

        Ok(())
    }

    #[tokio::test]
    async fn test_barter_pay_erc721_for_erc1155() -> eyre::Result<()> {
        // test setup
        let test = setup_test_environment().await?;

        // mint an ERC721 token to alice (she will fulfill with this)
        let mock_erc721_a = MockERC721::new(test.mock_addresses.erc721_a, &test.god_provider);
        mock_erc721_a
            .mint(test.alice.address())
            .send()
            .await?
            .get_receipt()
            .await?;

        // give bob some ERC1155 tokens for escrow
        let mock_erc1155_a = MockERC1155::new(test.mock_addresses.erc1155_a, &test.god_provider);
        mock_erc1155_a
            .mint(test.bob.address(), U256::from(1), U256::from(10))
            .send()
            .await?
            .get_receipt()
            .await?;

        // begin test
        let bid = Erc1155Data {
            // bob's bid
            address: test.mock_addresses.erc1155_a,
            id: U256::from(1),
            value: U256::from(10),
        };
        let ask = Erc721Data {
            // bob asks for alice's ERC721
            address: test.mock_addresses.erc721_a,
            id: U256::from(1),
        };

        // bob approves tokens for barter and creates buy attestation
        test.bob_client
            .erc1155()
            .approve_all(test.mock_addresses.erc1155_a, ApprovalPurpose::BarterUtils)
            .await?;

        let buy_receipt = test
            .bob_client
            .erc1155()
            .barter()
            .buy_erc721_with_erc1155(&bid, &ask, 0)
            .await?;

        let buy_attestation = DefaultAlkahestClient::get_attested_event(buy_receipt)?.uid;

        // alice approves her token for barter
        test.alice_client
            .erc721()
            .approve(&ask, ApprovalPurpose::BarterUtils)
            .await?;

        // alice fulfills the buy attestation
        let _sell_receipt = test
            .alice_client
            .erc721()
            .barter()
            .pay_erc721_for_erc1155(buy_attestation)
            .await?;

        // verify token transfers
        let alice_erc1155_balance = mock_erc1155_a
            .balanceOf(test.alice.address(), U256::from(1))
            .call()
            .await?;

        let bob_token_owner = mock_erc721_a.ownerOf(U256::from(1)).call().await?;

        // both sides received the tokens
        assert_eq!(
            alice_erc1155_balance,
            U256::from(10),
            "Alice should have received ERC1155 tokens"
        );
        assert_eq!(
            bob_token_owner,
            test.bob.address(),
            "Bob should have received the ERC721 token"
        );

        Ok(())
    }

    #[tokio::test]
    async fn test_barter_pay_erc721_for_bundle() -> eyre::Result<()> {
        // test setup
        let test = setup_test_environment().await?;

        // mint an ERC721 token to alice (she will fulfill with this)
        let mock_erc721_a = MockERC721::new(test.mock_addresses.erc721_a, &test.god_provider);
        mock_erc721_a
            .mint(test.alice.address())
            .send()
            .await?
            .get_receipt()
            .await?;

        // give bob tokens for the bundle (he will escrow these)
        // ERC20
        let mock_erc20_b = MockERC20Permit::new(test.mock_addresses.erc20_b, &test.god_provider);
        mock_erc20_b
            .transfer(test.bob.address(), U256::from(20))
            .send()
            .await?
            .get_receipt()
            .await?;

        // ERC721
        let mock_erc721_b = MockERC721::new(test.mock_addresses.erc721_b, &test.god_provider);
        mock_erc721_b
            .mint(test.bob.address())
            .send()
            .await?
            .get_receipt()
            .await?;

        // ERC1155
        let mock_erc1155_a = MockERC1155::new(test.mock_addresses.erc1155_a, &test.god_provider);
        mock_erc1155_a
            .mint(test.bob.address(), U256::from(1), U256::from(5))
            .send()
            .await?
            .get_receipt()
            .await?;

        // begin test
        // Check balances before the exchange
        let initial_alice_erc20_balance =
            mock_erc20_b.balanceOf(test.alice.address()).call().await?;
        let initial_alice_erc1155_balance = mock_erc1155_a
            .balanceOf(test.alice.address(), U256::from(1))
            .call()
            .await?;

        // Bob's bundle that he'll escrow
        let bundle = TokenBundleData {
            native_amount: U256::ZERO,
            erc20s: vec![Erc20Data {
                address: test.mock_addresses.erc20_b,
                value: U256::from(20),
            }],
            erc721s: vec![Erc721Data {
                address: test.mock_addresses.erc721_b,
                id: U256::from(1),
            }],
            erc1155s: vec![Erc1155Data {
                address: test.mock_addresses.erc1155_a,
                id: U256::from(1),
                value: U256::from(5),
            }],
        };

        // Create the ERC721 payment obligation data as the demand
        let payment_obligation_data = crate::contracts::obligations::ERC721PaymentObligation::ObligationData {
            token: test.mock_addresses.erc721_a,
            tokenId: U256::from(1),
            payee: test.bob.address(),
        };

        // bob approves all tokens for the bundle escrow
        test.bob_client
            .token_bundle()
            .approve(&bundle, ApprovalPurpose::Escrow)
            .await?;

        // bob creates bundle escrow demanding ERC721 from Alice
        let buy_receipt = test
            .bob_client
            .token_bundle()
            .escrow()
            .non_tierable()
            .create(
                &bundle,
                &ArbiterData {
                    arbiter: test.addresses.erc721_addresses.payment_obligation,
                    demand: payment_obligation_data.abi_encode().into(),
                },
                0,
            )
            .await?;

        let buy_attestation = DefaultAlkahestClient::get_attested_event(buy_receipt)?.uid;

        // alice approves her ERC721 for barter
        test.alice_client
            .erc721()
            .approve(
                &Erc721Data {
                    address: test.mock_addresses.erc721_a,
                    id: U256::from(1),
                },
                ApprovalPurpose::BarterUtils,
            )
            .await?;

        // alice fulfills bob's buy attestation with her ERC721
        let pay_receipt = test
            .alice_client
            .erc721()
            .barter()
            .pay_erc721_for_bundle(buy_attestation)
            .await?;

        // Verify the payment attestation was created
        let pay_attestation = DefaultAlkahestClient::get_attested_event(pay_receipt)?;
        assert_ne!(pay_attestation.uid, FixedBytes::<32>::default());

        // verify token transfers
        // Check alice received all tokens from the bundle
        let final_alice_erc20_balance = mock_erc20_b.balanceOf(test.alice.address()).call().await?;

        let alice_erc721_owner = mock_erc721_b.ownerOf(U256::from(1)).call().await?;

        let final_alice_erc1155_balance = mock_erc1155_a
            .balanceOf(test.alice.address(), U256::from(1))
            .call()
            .await?;

        // Check bob received the ERC721 token
        let bob_token_owner = mock_erc721_a.ownerOf(U256::from(1)).call().await?;

        // Verify alice received the bundle
        assert_eq!(
            final_alice_erc20_balance - initial_alice_erc20_balance,
            U256::from(20),
            "Alice should have received ERC20 tokens"
        );
        assert_eq!(
            alice_erc721_owner,
            test.alice.address(),
            "Alice should have received the ERC721 token from bundle"
        );
        assert_eq!(
            final_alice_erc1155_balance - initial_alice_erc1155_balance,
            U256::from(5),
            "Alice should have received ERC1155 tokens"
        );

        // Verify bob received the ERC721
        assert_eq!(
            bob_token_owner,
            test.bob.address(),
            "Bob should have received the ERC721 token"
        );

        Ok(())
    }

    #[tokio::test]
    async fn test_approve_and_pay_with_erc721() -> eyre::Result<()> {
        // test setup
        let test = setup_test_environment().await?;

        // mint ERC721 to alice
        let mock_erc721_a = MockERC721::new(test.mock_addresses.erc721_a, &test.god_provider);
        mock_erc721_a
            .mint(test.alice.address())
            .send()
            .await?
            .get_receipt()
            .await?;

        let price = Erc721Data {
            address: test.mock_addresses.erc721_a,
            id: U256::from(1),
        };

        // alice makes direct payment to bob using approve_and_pay (no pre-approval needed)
        let (approval_receipt, payment_receipt) = test
            .alice_client
            .erc721()
            .payment()
            .approve_and_pay(&price, test.bob.address())
            .await?;

        // Verify both receipts are valid
        assert!(approval_receipt.status(), "Approval should succeed");
        assert!(payment_receipt.status(), "Payment should succeed");

        // Verify payment happened - bob now owns the NFT
        let final_owner = mock_erc721_a.ownerOf(U256::from(1)).call().await?;
        assert_eq!(
            final_owner,
            test.bob.address(),
            "Bob should now own the NFT"
        );

        // payment obligation made
        let attested_event = DefaultAlkahestClient::get_attested_event(payment_receipt)?;
        assert_ne!(attested_event.uid, FixedBytes::<32>::default());

        Ok(())
    }

    #[tokio::test]
    async fn test_approve_and_create_escrow_with_erc721() -> eyre::Result<()> {
        // test setup
        let test = setup_test_environment().await?;

        // mint ERC721 to alice
        let mock_erc721_a = MockERC721::new(test.mock_addresses.erc721_a, &test.god_provider);
        mock_erc721_a
            .mint(test.alice.address())
            .send()
            .await?
            .get_receipt()
            .await?;

        let price = Erc721Data {
            address: test.mock_addresses.erc721_a,
            id: U256::from(1),
        };

        // Create custom arbiter data
        let arbiter = test.addresses.erc721_addresses.clone().payment_obligation;
        let demand = Bytes::from(b"custom demand data");
        let item = ArbiterData { arbiter, demand };

        // alice creates escrow using approve_and_create (no pre-approval needed)
        let (approval_receipt, escrow_receipt) = test
            .alice_client
            .erc721()
            .escrow()
            .non_tierable()
            .approve_and_create(&price, &item, 0)
            .await?;

        // Verify both receipts are valid
        assert!(approval_receipt.status(), "Approval should succeed");
        assert!(escrow_receipt.status(), "Escrow creation should succeed");

        // Verify escrow happened - escrow contract now owns the NFT
        let final_owner = mock_erc721_a.ownerOf(U256::from(1)).call().await?;
        assert_eq!(
            final_owner, test.addresses.erc721_addresses.escrow_obligation_nontierable,
            "Escrow contract should now own the NFT"
        );

        // escrow obligation made
        let attested_event = DefaultAlkahestClient::get_attested_event(escrow_receipt)?;
        assert_ne!(attested_event.uid, FixedBytes::<32>::default());

        Ok(())
    }
}
