import json

import httpx
import pytest
import respx

from horatio_data_provider import DataProviderClient

BASE = "http://test"


@pytest.fixture
def client():
    return DataProviderClient(BASE)


# --- AAVE ---

@respx.mock
@pytest.mark.asyncio
async def test_aave_deposit_event_routing(client, parquet_bytes):
    route = respx.post(f"{BASE}/aave/read").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await client.aave.deposits().network("ETH").time_range("2025-01-01", "2025-01-02").as_pandas()
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["event"] == "deposit"


@respx.mock
@pytest.mark.asyncio
async def test_aave_borrow_event_routing(client, parquet_bytes):
    route = respx.post(f"{BASE}/aave/read").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await client.aave.borrows().network("ETH").time_range("2025-01-01", "2025-01-02").as_pandas()
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["event"] == "borrow"


# --- Uniswap ---

@respx.mock
@pytest.mark.asyncio
async def test_uniswap_swaps_body(client, parquet_bytes):
    route = respx.post(f"{BASE}/uniswap/read").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await client.uniswap.swaps("WETH", "USDC", fee=500).network("ETH").time_range("2025-01-01", "2025-01-02").as_pandas()
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["event"] == "swap"
    assert body["symbol0"] == "WETH"
    assert body["symbol1"] == "USDC"
    assert body["fee"] == 500


# --- Lido ---

@respx.mock
@pytest.mark.asyncio
async def test_lido_deposit_event_routing(client, parquet_bytes):
    route = respx.post(f"{BASE}/lido/read").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await client.lido.deposits().network("ETH").time_range("2025-01-01", "2025-01-02").as_pandas()
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["event"] == "deposit"


@respx.mock
@pytest.mark.asyncio
async def test_lido_withdrawal_request_event_routing(client, parquet_bytes):
    route = respx.post(f"{BASE}/lido/read").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await client.lido.withdrawal_requests().network("ETH").time_range("2025-01-01", "2025-01-02").as_pandas()
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["event"] == "withdrawal_request"


# --- Stader ---

@respx.mock
@pytest.mark.asyncio
async def test_stader_deposit_event_routing(client, parquet_bytes):
    route = respx.post(f"{BASE}/stader/read").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await client.stader.deposits().network("ETH").time_range("2025-01-01", "2025-01-02").as_pandas()
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["event"] == "deposit"


# --- Threshold ---

@respx.mock
@pytest.mark.asyncio
async def test_threshold_deposit_request_event_routing(client, parquet_bytes):
    route = respx.post(f"{BASE}/threshold/read").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await client.threshold.deposit_requests().network("ETH").time_range("2025-01-01", "2025-01-02").as_pandas()
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["event"] == "deposit_request"


# --- Native ---

@respx.mock
@pytest.mark.asyncio
async def test_native_transfers_standard_url(client, parquet_bytes):
    route = respx.post(f"{BASE}/native_transfers/read").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await client.native.transfers().network("ETH").time_range("2025-01-01", "2025-01-02").as_pandas()
    assert route.called


@respx.mock
@pytest.mark.asyncio
async def test_native_transfers_min_amount_url(client, parquet_bytes):
    route = respx.post(f"{BASE}/native_transfers/read/min").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await client.native.transfers().network("ETH").time_range("2025-01-01", "2025-01-02").min_amount(0.5).as_pandas()
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["min_amount"] == 0.5


# --- Lido L2 events ---

@respx.mock
@pytest.mark.asyncio
async def test_lido_l2_deposit_event_routing(client, parquet_bytes):
    route = respx.post(f"{BASE}/lido/read").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await client.lido.l2_deposits().network("ARB").time_range("2025-01-01", "2025-01-02").as_pandas()
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["event"] == "l2_deposit"


@respx.mock
@pytest.mark.asyncio
async def test_lido_l2_withdrawal_request_event_routing(client, parquet_bytes):
    route = respx.post(f"{BASE}/lido/read").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await client.lido.l2_withdrawal_requests().network("OP").time_range("2025-01-01", "2025-01-02").as_pandas()
    body = json.loads(route.calls[0].request.content)
    assert body["event"] == "l2_withdrawal_request"


# --- AAVE eth_market_type and aggregate ---

@respx.mock
@pytest.mark.asyncio
async def test_aave_eth_market_type_in_body(client, parquet_bytes):
    route = respx.post(f"{BASE}/aave/read").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await (
        client.aave.deposits()
        .network("ETH")
        .time_range("2025-01-01", "2025-01-02")
        .eth_market_type("Prime")
        .as_pandas()
    )
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["eth_market_type"] == "Prime"


@respx.mock
@pytest.mark.asyncio
async def test_aave_aggregate_routes_to_aggregate_endpoint(client, parquet_bytes):
    route = respx.post(f"{BASE}/aave/aggregate").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await (
        client.aave.deposits()
        .network("ETH")
        .time_range("2025-01-01", "2025-01-02")
        .aggregate(group_by="time", period="1h")
        .as_pandas()
    )
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["aggregate"] is True


# --- Native aggregate ---

@respx.mock
@pytest.mark.asyncio
async def test_native_aggregate_routes_to_aggregate_endpoint(client, parquet_bytes):
    route = respx.post(f"{BASE}/native_transfers/aggregate").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await (
        client.native.transfers()
        .network("ETH")
        .time_range("2025-01-01", "2025-01-02")
        .aggregate()
        .as_pandas()
    )
    assert route.called


# --- BaseQuery filter methods ---

@respx.mock
@pytest.mark.asyncio
async def test_with_value_sets_body_field(client, parquet_bytes):
    route = respx.post(f"{BASE}/lido/read").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await client.lido.deposits().network("ETH").time_range("2025-01-01", "2025-01-02").with_value().as_pandas()
    body = json.loads(route.calls[0].request.content)
    assert body["with_value"] is True


@respx.mock
@pytest.mark.asyncio
async def test_involving_sets_body_field(client, parquet_bytes):
    route = respx.post(f"{BASE}/stader/read").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await client.stader.deposits().network("ETH").time_range("2025-01-01", "2025-01-02").involving("0xdeadbeef").as_pandas()
    body = json.loads(route.calls[0].request.content)
    assert body["involving"] == "0xdeadbeef"


@respx.mock
@pytest.mark.asyncio
async def test_threshold_aggregate_routes_to_aggregate_endpoint(client, parquet_bytes):
    route = respx.post(f"{BASE}/threshold/aggregate").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await (
        client.threshold.deposits()
        .network("ETH")
        .time_range("2025-01-01", "2025-01-02")
        .aggregate()
        .as_pandas()
    )
    assert route.called


# --- Cache ---

@respx.mock
@pytest.mark.asyncio
async def test_cache_flush(client):
    route = respx.post(f"{BASE}/cache/flush").mock(
        return_value=httpx.Response(200, content=b"{}", headers={"content-type": "application/json"})
    )
    await client.cache.flush()
    assert route.called


# --- EventQuery .cache() ---

@respx.mock
@pytest.mark.asyncio
async def test_event_query_cache_sets_body_fields(client, parquet_bytes):
    route = respx.post(f"{BASE}/aave/read").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await (
        client.aave.deposits()
        .network("ETH")
        .time_range("2025-01-01", "2025-01-02")
        .cache("force_replace")
        .as_pandas()
    )
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["cache"] is True
    assert body["cache_type"] == "force_replace"


@respx.mock
@pytest.mark.asyncio
async def test_event_query_cache_default_type_is_append(client, parquet_bytes):
    route = respx.post(f"{BASE}/lido/read").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await (
        client.lido.deposits()
        .network("ETH")
        .time_range("2025-01-01", "2025-01-02")
        .cache()
        .as_pandas()
    )
    body = json.loads(route.calls[0].request.content)
    assert body["cache"] is True
    assert body["cache_type"] == "append"


# --- NativeTransfersQuery .cache() ---

@respx.mock
@pytest.mark.asyncio
async def test_native_transfers_cache_sets_body_fields(client, parquet_bytes):
    route = respx.post(f"{BASE}/native_transfers/read").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    await (
        client.native.transfers()
        .network("ETH")
        .time_range("2025-01-01", "2025-01-02")
        .cache("replace")
        .as_pandas()
    )
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["cache"] is True
    assert body["cache_type"] == "replace"


# --- Flush methods ---

@respx.mock
@pytest.mark.asyncio
async def test_aave_flush_with_filters(client):
    route = respx.post(f"{BASE}/aave/flush").mock(
        return_value=httpx.Response(200, content=b"{}", headers={"content-type": "application/json"})
    )
    await client.aave.flush(network="ETH", event="deposit")
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["network"] == "ETH"
    assert body["event"] == "deposit"


@respx.mock
@pytest.mark.asyncio
async def test_uniswap_flush_with_filters(client):
    route = respx.post(f"{BASE}/uniswap/flush").mock(
        return_value=httpx.Response(200, content=b"{}", headers={"content-type": "application/json"})
    )
    await client.uniswap.flush(network="ETH", event="swap", symbol0="WETH", symbol1="USDC", fee=500)
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body == {"network": "ETH", "event": "swap", "symbol0": "WETH", "symbol1": "USDC", "fee": 500}


@respx.mock
@pytest.mark.asyncio
async def test_lido_flush_empty_body(client):
    route = respx.post(f"{BASE}/lido/flush").mock(
        return_value=httpx.Response(200, content=b"{}", headers={"content-type": "application/json"})
    )
    await client.lido.flush()
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body == {}


@respx.mock
@pytest.mark.asyncio
async def test_stader_flush_with_network(client):
    route = respx.post(f"{BASE}/stader/flush").mock(
        return_value=httpx.Response(200, content=b"{}", headers={"content-type": "application/json"})
    )
    await client.stader.flush(network="ETH")
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["network"] == "ETH"


@respx.mock
@pytest.mark.asyncio
async def test_threshold_flush_with_event(client):
    route = respx.post(f"{BASE}/threshold/flush").mock(
        return_value=httpx.Response(200, content=b"{}", headers={"content-type": "application/json"})
    )
    await client.threshold.flush(event="deposit_request")
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["event"] == "deposit_request"


@respx.mock
@pytest.mark.asyncio
async def test_native_flush_with_network(client):
    route = respx.post(f"{BASE}/native_transfers/flush").mock(
        return_value=httpx.Response(200, content=b"{}", headers={"content-type": "application/json"})
    )
    await client.native.flush(network="ETH")
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["network"] == "ETH"


# --- flush_aggregate methods ---

@respx.mock
@pytest.mark.asyncio
async def test_aave_flush_aggregate(client):
    route = respx.post(f"{BASE}/aave/aggregate/flush").mock(
        return_value=httpx.Response(200, content=b"{}", headers={"content-type": "application/json"})
    )
    await client.aave.flush_aggregate(network="ETH", event="deposit", period="1h")
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["network"] == "ETH"
    assert body["event"] == "deposit"
    assert body["period"] == "1h"


@respx.mock
@pytest.mark.asyncio
async def test_uniswap_flush_aggregate(client):
    route = respx.post(f"{BASE}/uniswap/aggregate/flush").mock(
        return_value=httpx.Response(200, content=b"{}", headers={"content-type": "application/json"})
    )
    await client.uniswap.flush_aggregate(network="ETH", event="swap", symbol0="WETH", symbol1="USDC", fee=500)
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body == {"network": "ETH", "event": "swap", "symbol0": "WETH", "symbol1": "USDC", "fee": 500}


@respx.mock
@pytest.mark.asyncio
async def test_lido_flush_aggregate(client):
    route = respx.post(f"{BASE}/lido/aggregate/flush").mock(
        return_value=httpx.Response(200, content=b"{}", headers={"content-type": "application/json"})
    )
    await client.lido.flush_aggregate(network="ETH")
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["network"] == "ETH"


@respx.mock
@pytest.mark.asyncio
async def test_stader_flush_aggregate(client):
    route = respx.post(f"{BASE}/stader/aggregate/flush").mock(
        return_value=httpx.Response(200, content=b"{}", headers={"content-type": "application/json"})
    )
    await client.stader.flush_aggregate(network="ETH", event="deposit")
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body == {"network": "ETH", "event": "deposit"}


@respx.mock
@pytest.mark.asyncio
async def test_threshold_flush_aggregate(client):
    route = respx.post(f"{BASE}/threshold/aggregate/flush").mock(
        return_value=httpx.Response(200, content=b"{}", headers={"content-type": "application/json"})
    )
    await client.threshold.flush_aggregate(event="deposit")
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["event"] == "deposit"


@respx.mock
@pytest.mark.asyncio
async def test_native_flush_aggregate(client):
    route = respx.post(f"{BASE}/native_transfers/aggregate/flush").mock(
        return_value=httpx.Response(200, content=b"{}", headers={"content-type": "application/json"})
    )
    await client.native.flush_aggregate(network="ETH", period="1d")
    assert route.called
    body = json.loads(route.calls[0].request.content)
    assert body["network"] == "ETH"
    assert body["period"] == "1d"
