import json

import httpx
import pytest
import respx

from horatio_data_provider import DataProviderClient, DataProviderHTTPError
from .conftest import make_parquet_bytes
import pandas as pd

BASE = "http://test"


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


@respx.mock
@pytest.mark.asyncio
async def test_transfers_correct_url_and_body(client, parquet_bytes):
    route = respx.post(f"{BASE}/erc20_transfers/read").mock(
        return_value=httpx.Response(200, content=parquet_bytes, headers={"content-type": "application/octet-stream"})
    )
    df = await client.erc20.transfers(["USDT", "USDC"]).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["tokens"] == ["USDT", "USDC"]
    assert body["network"] == "ETH"
    assert body["since"] == "2025-01-01T00:00:00Z"
    assert body["until"] == "2025-01-02T00:00:00Z"
    assert isinstance(df, pd.DataFrame)


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


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


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


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


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


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


@respx.mock
@pytest.mark.asyncio
async def test_http_error_propagation(client):
    respx.post(f"{BASE}/erc20_transfers/read").mock(
        return_value=httpx.Response(
            400,
            json={"error": "bad request"},
            headers={"content-type": "application/json"},
        )
    )
    with pytest.raises(DataProviderHTTPError) as exc_info:
        await client.erc20.transfers(["USDT"]).network("ETH").time_range("2025-01-01", "2025-01-02").as_pandas()
    assert exc_info.value.status_code == 400
    assert "bad request" in str(exc_info.value)


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


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