#![cfg(feature = "ctf")]
#![allow(clippy::unwrap_used, reason = "Fine for tests")]

use alloy::primitives::{B256, U256};
use alloy::providers::ProviderBuilder;
use httpmock::{Method::POST, MockServer};
use kuest_client_sdk::POLYGON;
use kuest_client_sdk::ctf::Client;
use kuest_client_sdk::types::address;
use serde_json::json;

mod contract_calls {
    use alloy::primitives::b256;
    use kuest_client_sdk::ctf::types::{
        CollectionIdRequest, ConditionIdRequest, PositionIdRequest,
    };

    use super::*;

    #[tokio::test]
    async fn get_condition_id() -> anyhow::Result<()> {
        let server = MockServer::start();
        let provider = ProviderBuilder::new().connect(&server.base_url()).await?;
        let client = Client::new(provider, POLYGON)?;

        // Mock the eth_call JSON-RPC response
        let mock = server.mock(|when, then| {
            when.method(POST).path("/");
            then.json_body(json!({
                "jsonrpc": "2.0",
                "id": 1,
                "result": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
            }));
        });

        let request = ConditionIdRequest::builder()
            .oracle(address!("0x0000000000000000000000000000000000000001"))
            .question_id(B256::ZERO)
            .outcome_slot_count(U256::from(2))
            .build();

        let response = client.condition_id(&request).await?;

        // Verify we got the mocked response
        assert_eq!(
            response.condition_id,
            b256!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")
        );
        mock.assert();

        Ok(())
    }

    #[tokio::test]
    async fn get_collection_id() -> anyhow::Result<()> {
        let server = MockServer::start();
        let provider = ProviderBuilder::new().connect(&server.base_url()).await?;
        let client = Client::new(provider, POLYGON)?;

        let mock = server.mock(|when, then| {
            when.method(POST).path("/");
            then.json_body(json!({
                "jsonrpc": "2.0",
                "id": 1,
                "result": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd"
            }));
        });

        let request = CollectionIdRequest::builder()
            .parent_collection_id(B256::ZERO)
            .condition_id(B256::ZERO)
            .index_set(U256::from(1))
            .build();

        let response = client.collection_id(&request).await?;

        // Verify we got the mocked response
        assert_eq!(
            response.collection_id,
            b256!("abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd")
        );
        mock.assert();

        Ok(())
    }

    #[tokio::test]
    async fn get_position_id() -> anyhow::Result<()> {
        let server = MockServer::start();
        let provider = ProviderBuilder::new().connect(&server.base_url()).await?;
        let client = Client::new(provider, POLYGON)?;

        let mock = server.mock(|when, then| {
            when.method(POST).path("/");
            then.json_body(json!({
                "jsonrpc": "2.0",
                "id": 1,
                "result": "0x00000000000000000000000000000000000000000000000000000000000000ff"
            }));
        });

        let usdc = address!("0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359");

        let request = PositionIdRequest::builder()
            .collateral_token(usdc)
            .collection_id(B256::ZERO)
            .build();

        let response = client.position_id(&request).await?;

        // Verify we got the mocked response
        assert_eq!(response.position_id, U256::from(0xff));
        mock.assert();

        Ok(())
    }
}

mod client_creation {
    use super::*;

    #[tokio::test]
    async fn polygon_mainnet_should_succeed() -> anyhow::Result<()> {
        let server = MockServer::start();
        let provider = ProviderBuilder::new().connect(&server.base_url()).await?;

        let client = Client::new(provider, POLYGON);
        client.unwrap();

        Ok(())
    }

    #[tokio::test]
    async fn amoy_testnet_should_succeed() -> anyhow::Result<()> {
        let server = MockServer::start();
        let provider = ProviderBuilder::new().connect(&server.base_url()).await?;

        let client = Client::new(provider, kuest_client_sdk::AMOY);
        client.unwrap();

        Ok(())
    }

    #[tokio::test]
    async fn invalid_chain_should_fail() -> anyhow::Result<()> {
        let server = MockServer::start();
        let provider = ProviderBuilder::new().connect(&server.base_url()).await?;

        let client = Client::new(provider, 999);
        client.unwrap_err();

        Ok(())
    }
}

mod request_builders {
    use kuest_client_sdk::ctf::types::{
        ConditionIdRequest, MergePositionsRequest, RedeemPositionsRequest, SplitPositionRequest,
    };

    use super::*;

    #[test]
    fn condition_id_request_builder() {
        let request = ConditionIdRequest::builder()
            .oracle(address!("0x0000000000000000000000000000000000000001"))
            .question_id(B256::ZERO)
            .outcome_slot_count(U256::from(2))
            .build();

        assert_eq!(
            request.oracle,
            address!("0x0000000000000000000000000000000000000001")
        );
        assert_eq!(request.question_id, B256::ZERO);
        assert_eq!(request.outcome_slot_count, U256::from(2));
    }

    #[test]
    fn split_position_request_builder() {
        let request = SplitPositionRequest::builder()
            .collateral_token(address!("0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"))
            .condition_id(B256::ZERO)
            .partition(vec![U256::from(1), U256::from(2)])
            .amount(U256::from(1_000_000))
            .build();

        assert_eq!(
            request.collateral_token,
            address!("0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359")
        );
        assert_eq!(request.parent_collection_id, B256::ZERO);
        assert_eq!(request.amount, U256::from(1_000_000));
    }

    #[test]
    fn merge_positions_request_builder() {
        let request = MergePositionsRequest::builder()
            .collateral_token(address!("0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"))
            .condition_id(B256::ZERO)
            .partition(vec![U256::from(1), U256::from(2)])
            .amount(U256::from(1_000_000))
            .build();

        assert_eq!(request.parent_collection_id, B256::ZERO);
        assert_eq!(request.amount, U256::from(1_000_000));
    }

    #[test]
    fn redeem_positions_request_builder() {
        let request = RedeemPositionsRequest::builder()
            .collateral_token(address!("0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"))
            .condition_id(B256::ZERO)
            .index_sets(vec![U256::from(1)])
            .build();

        assert_eq!(request.parent_collection_id, B256::ZERO);
        assert_eq!(request.index_sets, vec![U256::from(1)]);
    }
}

mod binary_market_convenience_methods {
    use kuest_client_sdk::ctf::types::{
        MergePositionsRequest, RedeemPositionsRequest, SplitPositionRequest,
    };

    use super::*;

    #[test]
    fn split_position_for_binary_market() {
        let usdc = address!("0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359");
        let condition_id = B256::ZERO;

        let request =
            SplitPositionRequest::for_binary_market(usdc, condition_id, U256::from(1_000_000));

        assert_eq!(request.collateral_token, usdc);
        assert_eq!(request.condition_id, condition_id);
        assert_eq!(request.partition, vec![U256::from(1), U256::from(2)]);
        assert_eq!(request.amount, U256::from(1_000_000));
        assert_eq!(request.parent_collection_id, B256::ZERO);
    }

    #[test]
    fn merge_positions_for_binary_market() {
        let usdc = address!("0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359");
        let condition_id = B256::ZERO;

        let request =
            MergePositionsRequest::for_binary_market(usdc, condition_id, U256::from(1_000_000));

        assert_eq!(request.collateral_token, usdc);
        assert_eq!(request.condition_id, condition_id);
        assert_eq!(request.partition, vec![U256::from(1), U256::from(2)]);
        assert_eq!(request.amount, U256::from(1_000_000));
    }

    #[test]
    fn redeem_positions_for_binary_market() {
        let usdc = address!("0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359");
        let condition_id = B256::ZERO;

        let request = RedeemPositionsRequest::for_binary_market(usdc, condition_id);

        assert_eq!(request.collateral_token, usdc);
        assert_eq!(request.condition_id, condition_id);
        assert_eq!(request.index_sets, vec![U256::from(1), U256::from(2)]);
    }
}

mod neg_risk {
    use kuest_client_sdk::ctf::types::RedeemNegRiskRequest;

    use super::*;

    #[test]
    fn redeem_neg_risk_request_builder() {
        let condition_id = B256::ZERO;
        let amounts = vec![U256::from(500_000), U256::from(500_000)];

        let request = RedeemNegRiskRequest::builder()
            .condition_id(condition_id)
            .amounts(amounts.clone())
            .build();

        assert_eq!(request.condition_id, condition_id);
        assert_eq!(request.amounts, amounts);
    }

    #[tokio::test]
    async fn client_with_neg_risk_should_succeed() -> anyhow::Result<()> {
        let server = MockServer::start();
        let provider = ProviderBuilder::new().connect(&server.base_url()).await?;

        let client = Client::with_neg_risk(provider, POLYGON);
        client.unwrap();

        Ok(())
    }
}
