"""Tests for boxtime.clients.ergo_node_client — ErgoNodeClient."""

from unittest.mock import AsyncMock, patch

from boxtime.models import Balance, BlockHeader, EmissionInfo, ErgoBox, Transaction
from tests.conftest import (
    BLOCK_HEADER_JSON,
    EMISSION_JSON,
    ERGO_BOX_JSON,
    TRANSACTION_JSON,
)

PATCH_TARGET = "boxtime.clients.ergo_node_client.async_http_get"


# ------------------------------------------------------------------
# Node info
# ------------------------------------------------------------------


async def test_get_node_info(ergo_client):
    with patch(PATCH_TARGET, new_callable=AsyncMock, return_value={"name": "ergo"}):
        result = await ergo_client.get_node_info()
    assert result == {"name": "ergo"}


async def test_get_node_info_failure(ergo_client):
    with patch(PATCH_TARGET, new_callable=AsyncMock, return_value=None):
        result = await ergo_client.get_node_info()
    assert result is None


async def test_get_indexed_height(ergo_client):
    with patch(
        PATCH_TARGET,
        new_callable=AsyncMock,
        return_value={"indexedHeight": 500_000},
    ):
        result = await ergo_client.get_indexed_height()
    assert result == 500_000


async def test_get_indexed_height_full_height_fallback(ergo_client):
    with patch(
        PATCH_TARGET,
        new_callable=AsyncMock,
        return_value={"fullHeight": 499_999, "indexedHeight": 0},
    ):
        result = await ergo_client.get_indexed_height()
    assert result == 499_999


async def test_get_indexed_height_failure(ergo_client):
    with patch(PATCH_TARGET, new_callable=AsyncMock, return_value=None):
        result = await ergo_client.get_indexed_height()
    assert result is None


# ------------------------------------------------------------------
# Transactions
# ------------------------------------------------------------------


async def test_get_transaction(ergo_client):
    with patch(
        PATCH_TARGET, new_callable=AsyncMock, return_value=TRANSACTION_JSON
    ):
        result = await ergo_client.get_transaction("tx123")
    assert isinstance(result, Transaction)
    assert result.id == "tx123"
    assert result.inclusion_height == 1000
    assert len(result.inputs) == 1
    assert result.inputs[0].value == 500_000_000


async def test_get_transaction_failure(ergo_client):
    with patch(PATCH_TARGET, new_callable=AsyncMock, return_value=None):
        result = await ergo_client.get_transaction("tx123")
    assert result is None


async def test_get_transaction_by_index(ergo_client):
    with patch(
        PATCH_TARGET, new_callable=AsyncMock, return_value=TRANSACTION_JSON
    ):
        result = await ergo_client.get_transaction_by_index(5000)
    assert isinstance(result, Transaction)
    assert result.global_index == 5000


async def test_get_transactions_by_address(ergo_client):
    with patch(
        PATCH_TARGET, new_callable=AsyncMock, return_value=[TRANSACTION_JSON]
    ):
        result = await ergo_client.get_transactions_by_address(
            "9f...", offset=0, limit=10
        )
    assert len(result) == 1
    assert isinstance(result[0], Transaction)


async def test_get_transactions_by_address_failure(ergo_client):
    with patch(PATCH_TARGET, new_callable=AsyncMock, return_value=None):
        result = await ergo_client.get_transactions_by_address("9f...")
    assert result is None


# ------------------------------------------------------------------
# Boxes
# ------------------------------------------------------------------


async def test_get_box(ergo_client):
    with patch(
        PATCH_TARGET, new_callable=AsyncMock, return_value=ERGO_BOX_JSON
    ):
        result = await ergo_client.get_box("box123")
    assert isinstance(result, ErgoBox)
    assert result.box_id == "box123"
    assert result.value == 1_000_000_000


async def test_get_box_failure(ergo_client):
    with patch(PATCH_TARGET, new_callable=AsyncMock, return_value=None):
        result = await ergo_client.get_box("box123")
    assert result is None


async def test_get_box_by_index(ergo_client):
    with patch(
        PATCH_TARGET, new_callable=AsyncMock, return_value=ERGO_BOX_JSON
    ):
        result = await ergo_client.get_box_by_index(42)
    assert isinstance(result, ErgoBox)


async def test_get_unspent_boxes_by_address(ergo_client):
    with patch(
        PATCH_TARGET, new_callable=AsyncMock, return_value=[ERGO_BOX_JSON]
    ):
        result = await ergo_client.get_unspent_boxes_by_address(
            "9f...", offset=0, limit=5
        )
    assert len(result) == 1
    assert isinstance(result[0], ErgoBox)


async def test_get_unspent_boxes_by_address_failure(ergo_client):
    with patch(PATCH_TARGET, new_callable=AsyncMock, return_value=None):
        result = await ergo_client.get_unspent_boxes_by_address("9f...")
    assert result is None


# ------------------------------------------------------------------
# Blocks
# ------------------------------------------------------------------


async def test_get_block_header_at_height(ergo_client):
    mock = AsyncMock(side_effect=[["header123"], BLOCK_HEADER_JSON])
    with patch(PATCH_TARGET, mock):
        result = await ergo_client.get_block_header_at_height(1000)
    assert isinstance(result, BlockHeader)
    assert result.height == 1000
    assert result.id == "header123"


async def test_get_block_header_at_height_no_ids(ergo_client):
    with patch(PATCH_TARGET, new_callable=AsyncMock, return_value=[]):
        result = await ergo_client.get_block_header_at_height(1000)
    assert result is None


async def test_get_chain_slice(ergo_client):
    with patch(
        PATCH_TARGET,
        new_callable=AsyncMock,
        return_value=[BLOCK_HEADER_JSON],
    ):
        result = await ergo_client.get_chain_slice(1000, 1001)
    assert len(result) == 1
    assert isinstance(result[0], BlockHeader)


async def test_get_chain_slice_failure(ergo_client):
    with patch(PATCH_TARGET, new_callable=AsyncMock, return_value=None):
        result = await ergo_client.get_chain_slice(1000, 1001)
    assert result is None


async def test_get_block_transaction_ids(ergo_client):
    mock = AsyncMock(
        side_effect=[
            ["header123"],
            {"transactions": [{"id": "tx1"}, {"id": "tx2"}]},
        ]
    )
    with patch(PATCH_TARGET, mock):
        result = await ergo_client.get_block_transaction_ids(1000)
    assert result == ["tx1", "tx2"]


async def test_get_block_transaction_ids_no_block(ergo_client):
    with patch(PATCH_TARGET, new_callable=AsyncMock, return_value=[]):
        result = await ergo_client.get_block_transaction_ids(1000)
    assert result is None


async def test_get_block_transaction_ids_no_txs(ergo_client):
    mock = AsyncMock(side_effect=[["header123"], None])
    with patch(PATCH_TARGET, mock):
        result = await ergo_client.get_block_transaction_ids(1000)
    assert result is None


# ------------------------------------------------------------------
# Emission
# ------------------------------------------------------------------


async def test_get_emission_at(ergo_client):
    with patch(
        PATCH_TARGET, new_callable=AsyncMock, return_value=EMISSION_JSON
    ):
        result = await ergo_client.get_emission_at(1000)
    assert isinstance(result, EmissionInfo)
    assert result.total_coins_issued == 50_000_000_000_000_000
    assert result.miner_reward == 67500000000


async def test_get_emission_at_failure(ergo_client):
    with patch(PATCH_TARGET, new_callable=AsyncMock, return_value=None):
        result = await ergo_client.get_emission_at(1000)
    assert result is None


# ------------------------------------------------------------------
# Balance
# ------------------------------------------------------------------


async def test_get_address_balance(ergo_client):
    boxes = [
        {"value": 1_000_000_000, "assets": [{"tokenId": "tok1", "amount": 100}]},
        {"value": 2_000_000_000, "assets": [{"tokenId": "tok1", "amount": 50}]},
    ]
    with patch(PATCH_TARGET, new_callable=AsyncMock, return_value=boxes):
        result = await ergo_client.get_address_balance("9f...")
    assert isinstance(result, Balance)
    assert result.nano_ergs == 3_000_000_000
    assert len(result.tokens) == 1
    assert result.tokens[0].amount == 150


async def test_get_address_balance_failure(ergo_client):
    with patch(PATCH_TARGET, new_callable=AsyncMock, return_value=None):
        result = await ergo_client.get_address_balance("9f...")
    assert result is None
