#![cfg(all(feature = "clob", feature = "rfq"))]
#![allow(
    clippy::unwrap_used,
    reason = "Do not need additional syntax for setting up tests"
)]

mod common;

use alloy::primitives::Address;
use httpmock::MockServer;
use kuest_client_sdk::clob::types::{
    AcceptRfqQuoteRequest, ApproveRfqOrderRequest, CancelRfqQuoteRequest, CancelRfqRequestRequest,
    CreateRfqQuoteRequest, CreateRfqRequestRequest, RfqQuotesRequest, RfqRequestsRequest, Side,
    SignatureType,
};
use reqwest::StatusCode;
use rust_decimal_macros::dec;
use serde_json::json;
use uuid::Uuid;

use crate::common::{KUEST_ADDRESS, create_authenticated};

mod request {
    use std::str::FromStr as _;

    use kuest_client_sdk::clob::types::request::Asset;
    use kuest_client_sdk::types::U256;

    use super::*;

    #[tokio::test]
    async fn rfq_create_request_should_succeed() -> anyhow::Result<()> {
        let server = MockServer::start();
        let client = create_authenticated(&server).await?;

        let mock = server.mock(|when, then| {
            when.method(httpmock::Method::POST)
                .path("/rfq/request")
                .header_exists(KUEST_ADDRESS)
                .json_body(json!({
                    "assetIn": "12345",
                    "assetOut": "0",
                    "amountIn": "50000000",
                    "amountOut": "3000000",
                    "userType": 3
                }));
            then.status(StatusCode::OK).json_body(json!({
                "requestId": "0196464a-a1fa-75e6-821e-31aa0794f7ad",
                "expiry": 1_744_936_318
            }));
        });

        let request = CreateRfqRequestRequest::builder()
            .asset_in(Asset::Asset(U256::from_str("12345")?))
            .asset_out(Asset::Usdc)
            .amount_in(dec!(50000000))
            .amount_out(dec!(3000000))
            .user_type(SignatureType::DepositWallet)
            .build();

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

        assert_eq!(response.request_id, "0196464a-a1fa-75e6-821e-31aa0794f7ad");
        assert_eq!(response.expiry, 1_744_936_318);
        mock.assert();

        Ok(())
    }

    #[tokio::test]
    async fn rfq_cancel_request_should_succeed() -> anyhow::Result<()> {
        let server = MockServer::start();
        let client = create_authenticated(&server).await?;

        let mock = server.mock(|when, then| {
            when.method(httpmock::Method::DELETE)
                .path("/rfq/request")
                .header_exists(KUEST_ADDRESS)
                .json_body(json!({
                    "requestId": "0196464a-a1fa-75e6-821e-31aa0794f7ad"
                }));
            then.status(StatusCode::OK).body("OK");
        });

        let request = CancelRfqRequestRequest::builder()
            .request_id("0196464a-a1fa-75e6-821e-31aa0794f7ad")
            .build();

        client.cancel_request(&request).await?;
        mock.assert();

        Ok(())
    }

    #[tokio::test]
    async fn rfq_requests_should_succeed() -> anyhow::Result<()> {
        let server = MockServer::start();
        let client = create_authenticated(&server).await?;

        let mock = server.mock(|when, then| {
            when.method(httpmock::Method::GET)
                .path("/rfq/data/requests")
                .header_exists(KUEST_ADDRESS);
            then.status(StatusCode::OK).json_body(json!({
                "data": [{
                    "requestId": "01968f1e-1182-71c4-9d40-172db9be82af",
                    "userAddress": "0x6e0c80c90ea6c15917308f820eac91ce2724b5b5",
                    "proxyAddress": "0x6e0c80c90ea6c15917308f820eac91ce2724b5b5",
                    "condition": "0x37a6a2dd9f3469495d9ec2467b0a764c5905371a294ce544bc3b2c944eb3e84a",
                    "token": "34097058504275310827233323421517291090691602969494795225921954353603704046623",
                    "complement": "32868290514114487320702931554221558599637733115139769311383916145370132125101",
                    "side": "BUY",
                    "sizeIn": 100,
                    "sizeOut": 50,
                    "price": 0.5,
                    "expiry": 1_746_159_634
                }],
                "next_cursor": "LTE=",
                "limit": 100,
                "count": 1
            }));
        });

        let request = RfqRequestsRequest::default();
        let response = client.requests(&request, None).await?;

        assert_eq!(response.count, 1);
        assert_eq!(response.data.len(), 1);
        assert_eq!(
            response.data[0].request_id,
            "01968f1e-1182-71c4-9d40-172db9be82af"
        );
        mock.assert();

        Ok(())
    }

    #[tokio::test]
    async fn rfq_requests_with_cursor_should_succeed() -> anyhow::Result<()> {
        let server = MockServer::start();
        let client = create_authenticated(&server).await?;

        let mock = server.mock(|when, then| {
            when.method(httpmock::Method::GET)
                .path("/rfq/data/requests")
                .query_param("next_cursor", "abc123")
                .header_exists(KUEST_ADDRESS);
            then.status(StatusCode::OK).json_body(json!({
                "data": [],
                "next_cursor": "",
                "limit": 100,
                "count": 0
            }));
        });

        let request = RfqRequestsRequest::default();
        let response = client.requests(&request, Some("abc123")).await?;

        assert_eq!(response.count, 0);
        mock.assert();

        Ok(())
    }
}

mod quote {
    use std::str::FromStr as _;

    use kuest_client_sdk::clob::types::request::Asset;
    use kuest_client_sdk::types::U256;

    use super::*;

    #[tokio::test]
    async fn rfq_create_quote_should_succeed() -> anyhow::Result<()> {
        let server = MockServer::start();
        let client = create_authenticated(&server).await?;

        let mock = server.mock(|when, then| {
            when.method(httpmock::Method::POST)
                .path("/rfq/quote")
                .header_exists(KUEST_ADDRESS)
                .json_body(json!({
                    "requestId": "01968f1e-1182-71c4-9d40-172db9be82af",
                    "assetIn": "0",
                    "assetOut": "12345",
                    "amountIn": "3000000",
                    "amountOut": "50000000",
                    "userType": 3
                }));
            then.status(StatusCode::OK).json_body(json!({
                "quoteId": "0196f484-9fbd-74c1-bfc1-75ac21c1cf84"
            }));
        });

        let request = CreateRfqQuoteRequest::builder()
            .request_id("01968f1e-1182-71c4-9d40-172db9be82af")
            .asset_in(Asset::Usdc)
            .asset_out(Asset::Asset(U256::from_str("12345")?))
            .amount_in(dec!(3000000))
            .amount_out(dec!(50000000))
            .user_type(SignatureType::DepositWallet)
            .build();

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

        assert_eq!(response.quote_id, "0196f484-9fbd-74c1-bfc1-75ac21c1cf84");
        mock.assert();

        Ok(())
    }

    #[tokio::test]
    async fn rfq_cancel_quote_should_succeed() -> anyhow::Result<()> {
        let server = MockServer::start();
        let client = create_authenticated(&server).await?;

        let mock = server.mock(|when, then| {
            when.method(httpmock::Method::DELETE)
                .path("/rfq/quote")
                .header_exists(KUEST_ADDRESS)
                .json_body(json!({
                    "quoteId": "0196f484-9fbd-74c1-bfc1-75ac21c1cf84"
                }));
            then.status(StatusCode::OK).body("OK");
        });

        let request = CancelRfqQuoteRequest::builder()
            .quote_id("0196f484-9fbd-74c1-bfc1-75ac21c1cf84")
            .build();

        client.cancel_quote(&request).await?;
        mock.assert();

        Ok(())
    }

    #[tokio::test]
    async fn rfq_quotes_should_succeed() -> anyhow::Result<()> {
        let server = MockServer::start();
        let client = create_authenticated(&server).await?;

        let mock = server.mock(|when, then| {
            when.method(httpmock::Method::GET)
                .path("/rfq/data/quotes")
                .header_exists(KUEST_ADDRESS);
            then.status(StatusCode::OK).json_body(json!({
                "data": [{
                    "quoteId": "0196f484-9fbd-74c1-bfc1-75ac21c1cf84",
                    "requestId": "01968f1e-1182-71c4-9d40-172db9be82af",
                    "userAddress": "0x6e0c80c90ea6c15917308f820eac91ce2724b5b5",
                    "proxyAddress": "0x6e0c80c90ea6c15917308f820eac91ce2724b5b5",
                    "condition": "0x37a6a2dd9f3469495d9ec2467b0a764c5905371a294ce544bc3b2c944eb3e84a",
                    "token": "34097058504275310827233323421517291090691602969494795225921954353603704046623",
                    "complement": "32868290514114487320702931554221558599637733115139769311383916145370132125101",
                    "side": "BUY",
                    "sizeIn": 100,
                    "sizeOut": 50,
                    "price": 0.5
                }],
                "next_cursor": "LTE=",
                "limit": 100,
                "count": 1
            }));
        });

        let request = RfqQuotesRequest::default();
        let response = client.quotes(&request, None).await?;

        assert_eq!(response.count, 1);
        assert_eq!(response.data.len(), 1);
        assert_eq!(
            response.data[0].quote_id,
            "0196f484-9fbd-74c1-bfc1-75ac21c1cf84"
        );
        mock.assert();

        Ok(())
    }
}

mod execution {
    use super::*;
    use crate::common::token_1;

    #[tokio::test]
    async fn rfq_accept_quote_should_succeed() -> anyhow::Result<()> {
        let server = MockServer::start();
        let client = create_authenticated(&server).await?;

        let maker: Address = "0x6e0c80c90ea6c15917308f820eac91ce2724b5b5".parse()?;

        let mock = server.mock(|when, then| {
            when.method(httpmock::Method::POST)
                .path("/rfq/request/accept")
                .header_exists(KUEST_ADDRESS);
            then.status(StatusCode::OK).body("OK");
        });

        let request = AcceptRfqQuoteRequest::builder()
            .request_id("01968f1e-1182-71c4-9d40-172db9be82af")
            .quote_id("0196f484-9fbd-74c1-bfc1-75ac21c1cf84")
            .maker_amount(dec!(50000000))
            .taker_amount(dec!(3000000))
            .token_id(token_1())
            .maker(maker)
            .signer(maker)
            .taker(Address::ZERO)
            .nonce(0)
            .expiration(0)
            .side(Side::Buy)
            .fee_rate_bps(0)
            .signature("0x1234")
            .salt("123")
            .owner(Uuid::nil())
            .build();

        client.accept_quote(&request).await?;
        mock.assert();

        Ok(())
    }

    #[tokio::test]
    async fn rfq_approve_order_should_succeed() -> anyhow::Result<()> {
        let server = MockServer::start();
        let client = create_authenticated(&server).await?;

        let maker: Address = "0x6e0c80c90ea6c15917308f820eac91ce2724b5b5".parse()?;

        let mock = server.mock(|when, then| {
            when.method(httpmock::Method::POST)
                .path("/rfq/quote/approve")
                .header_exists(KUEST_ADDRESS);
            then.status(StatusCode::OK).json_body(json!({
                "tradeIds": ["019af0f7-eb77-764f-b40f-6de8a3562e12"]
            }));
        });

        let request = ApproveRfqOrderRequest::builder()
            .request_id("01968f1e-1182-71c4-9d40-172db9be82af")
            .quote_id("0196f484-9fbd-74c1-bfc1-75ac21c1cf84")
            .maker_amount(dec!(50000000))
            .taker_amount(dec!(3000000))
            .token_id(token_1())
            .maker(maker)
            .signer(maker)
            .taker(Address::ZERO)
            .nonce(0)
            .expiration(0)
            .side(Side::Buy)
            .fee_rate_bps(0)
            .signature("0x1234")
            .salt("123")
            .owner(Uuid::nil())
            .build();

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

        assert_eq!(response.trade_ids.len(), 1);
        assert_eq!(
            response.trade_ids[0],
            "019af0f7-eb77-764f-b40f-6de8a3562e12"
        );
        mock.assert();

        Ok(())
    }
}

mod error_handling {
    use std::str::FromStr as _;

    use kuest_client_sdk::clob::types::request::Asset;
    use kuest_client_sdk::error::Kind;
    use kuest_client_sdk::types::U256;

    use super::*;

    #[tokio::test]
    async fn rfq_create_request_error_should_return_status() -> anyhow::Result<()> {
        let server = MockServer::start();
        let client = create_authenticated(&server).await?;

        let mock = server.mock(|when, then| {
            when.method(httpmock::Method::POST).path("/rfq/request");
            then.status(StatusCode::BAD_REQUEST)
                .body("Invalid request parameters");
        });

        let request = CreateRfqRequestRequest::builder()
            .asset_in(Asset::Asset(U256::from_str("12345")?))
            .asset_out(Asset::Usdc)
            .amount_in(dec!(50000000))
            .amount_out(dec!(3000000))
            .user_type(SignatureType::DepositWallet)
            .build();

        let result = client.create_request(&request).await;

        assert!(result.is_err());
        let err = result.unwrap_err();
        assert_eq!(err.kind(), Kind::Status);
        mock.assert();

        Ok(())
    }

    #[tokio::test]
    async fn rfq_cancel_request_error_should_return_status() -> anyhow::Result<()> {
        let server = MockServer::start();
        let client = create_authenticated(&server).await?;

        let mock = server.mock(|when, then| {
            when.method(httpmock::Method::DELETE).path("/rfq/request");
            then.status(StatusCode::NOT_FOUND).body("Request not found");
        });

        let request = CancelRfqRequestRequest::builder()
            .request_id("nonexistent")
            .build();

        let result = client.cancel_request(&request).await;

        assert!(result.is_err());
        let err = result.unwrap_err();
        assert_eq!(err.kind(), Kind::Status);
        mock.assert();

        Ok(())
    }
}
