mod common;

use common::mock_client;
use futures::TryStreamExt;
use wiremock::matchers::{method, path, query_param};
use wiremock::{Mock, ResponseTemplate};

#[tokio::test]
async fn list_exchanges_returns_parsed_items() {
    let (server, client) = mock_client().await;

    Mock::given(method("GET"))
        .and(path("/v1/exchanges"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!([
            { "code": "binance", "display_name": "Binance" },
            { "code": "okx_spot", "display_name": "OKX Spot" }
        ])))
        .mount(&server)
        .await;

    let exchanges = client.exchanges().list().await.unwrap();
    assert_eq!(exchanges.len(), 2);
    assert_eq!(exchanges[0].code, "binance");
    assert_eq!(exchanges[1].display_name, "OKX Spot");
}

#[tokio::test]
async fn list_instruments_sends_query_params_and_decodes_page() {
    let (server, client) = mock_client().await;

    Mock::given(method("GET"))
        .and(path("/v1/exchanges/binance/instruments"))
        .and(query_param("search", "BTC"))
        .and(query_param("limit", "10"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "items": [
                { "symbol": "BTCUSDT", "base": "BTC", "quote": "USDT", "active": true }
            ],
            "total": 1
        })))
        .mount(&server)
        .await;

    let page = client
        .exchanges()
        .list_instruments("binance")
        .search("BTC")
        .limit(10)
        .send()
        .await
        .unwrap();

    assert_eq!(page.items.len(), 1);
    assert_eq!(page.items[0].symbol, "BTCUSDT");
    assert_eq!(page.total, Some(1));
}

#[tokio::test]
async fn list_instruments_stream_paginates() {
    let (server, client) = mock_client().await;

    Mock::given(method("GET"))
        .and(path("/v1/exchanges/binance/instruments"))
        .and(query_param("limit", "2"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "items": [
                { "symbol": "BTCUSDT", "base": "BTC", "quote": "USDT", "active": true },
                { "symbol": "ETHUSDT", "base": "ETH", "quote": "USDT", "active": true }
            ],
            "next_page_token": "c_1"
        })))
        .up_to_n_times(1)
        .mount(&server)
        .await;

    Mock::given(method("GET"))
        .and(path("/v1/exchanges/binance/instruments"))
        .and(query_param("page_token", "c_1"))
        .and(query_param("limit", "2"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "items": [
                { "symbol": "SOLUSDT", "base": "SOL", "quote": "USDT", "active": true }
            ]
        })))
        .mount(&server)
        .await;

    let all: Vec<_> = client
        .exchanges()
        .list_instruments("binance")
        .limit(2)
        .stream()
        .try_collect()
        .await
        .unwrap();

    let symbols: Vec<_> = all.iter().map(|i| i.symbol.as_str()).collect();
    assert_eq!(symbols, vec!["BTCUSDT", "ETHUSDT", "SOLUSDT"]);
}

#[tokio::test]
async fn list_datastreams_sends_path_params_and_query_and_decodes_page() {
    let (server, client) = mock_client().await;

    Mock::given(method("GET"))
        .and(path(
            "/v1/exchanges/binance/instruments/BTCUSDT/datastreams",
        ))
        .and(query_param("stream_type", "trade"))
        .and(query_param("wire_format", "json"))
        .and(query_param("limit", "5"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "items": [
                {
                    "datastream_id": 42,
                    "exchange": "binance",
                    "instrument": "BTCUSDT",
                    "stream_type": "trade",
                    "wire_format": "json"
                }
            ],
            "total": 1
        })))
        .mount(&server)
        .await;

    let page = client
        .exchanges()
        .list_datastreams("binance", "BTCUSDT")
        .stream_type("trade")
        .wire_format("json")
        .limit(5)
        .send()
        .await
        .unwrap();

    assert_eq!(page.items.len(), 1);
    assert_eq!(page.items[0].datastream_id, 42);
    assert_eq!(page.items[0].exchange, "binance");
    assert_eq!(page.items[0].instrument, "BTCUSDT");
    assert_eq!(page.items[0].stream_type, "trade");
    assert_eq!(page.items[0].wire_format, "json");
    assert_eq!(page.total, Some(1));
}

#[tokio::test]
async fn list_datastreams_stream_paginates() {
    let (server, client) = mock_client().await;

    Mock::given(method("GET"))
        .and(path(
            "/v1/exchanges/binance/instruments/BTCUSDT/datastreams",
        ))
        .and(query_param("limit", "2"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "items": [
                { "datastream_id": 1, "exchange": "binance", "instrument": "BTCUSDT",
                  "stream_type": "trade", "wire_format": "json" },
                { "datastream_id": 2, "exchange": "binance", "instrument": "BTCUSDT",
                  "stream_type": "depth", "wire_format": "json" }
            ],
            "next_page_token": "c_1"
        })))
        .up_to_n_times(1)
        .mount(&server)
        .await;

    Mock::given(method("GET"))
        .and(path(
            "/v1/exchanges/binance/instruments/BTCUSDT/datastreams",
        ))
        .and(query_param("page_token", "c_1"))
        .and(query_param("limit", "2"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "items": [
                { "datastream_id": 3, "exchange": "binance", "instrument": "BTCUSDT",
                  "stream_type": "ticker", "wire_format": "json" }
            ]
        })))
        .mount(&server)
        .await;

    let all: Vec<_> = client
        .exchanges()
        .list_datastreams("binance", "BTCUSDT")
        .limit(2)
        .stream()
        .try_collect()
        .await
        .unwrap();

    let ids: Vec<_> = all.iter().map(|d| d.datastream_id).collect();
    assert_eq!(ids, vec![1, 2, 3]);
}
