"""Tests for GetBalanceTool (langchain-oneshot).

Verifies the LangChain tool correctly delegates to the OneShotClient
get_balance / aget_balance methods and returns JSON strings.
"""

from __future__ import annotations

import json
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from langchain_oneshot.tools.account import GetBalanceTool
from oneshot import OneShotClient

MOCK_BALANCE = {
    "on_chain_balance": "50.000000",
    "credits_balance": "10.25",
    "currency": "USDC",
    "address": "0x1234567890abcdef1234567890abcdef12345678",
    "chain_id": 8453,
}

# Deterministic test key
TEST_PRIVATE_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"


class TestGetBalanceTool:
    @pytest.fixture
    def mock_client(self):
        """Create a real OneShotClient then mock its HTTP methods."""
        client = OneShotClient(TEST_PRIVATE_KEY)
        client.get_balance = MagicMock(return_value=MOCK_BALANCE)
        client.aget_balance = AsyncMock(return_value=MOCK_BALANCE)
        return client

    @pytest.fixture
    def tool(self, mock_client):
        return GetBalanceTool(client=mock_client)

    def test_tool_name(self, tool):
        assert tool.name == "oneshot_get_balance"

    def test_tool_description_mentions_credits(self, tool):
        assert "credits" in tool.description.lower()

    def test_tool_description_mentions_free(self, tool):
        assert "free" in tool.description.lower()

    def test_run_calls_get_balance(self, tool, mock_client):
        result = tool._run()
        mock_client.get_balance.assert_called_once()
        parsed = json.loads(result)
        assert parsed["on_chain_balance"] == "50.000000"
        assert parsed["credits_balance"] == "10.25"
        assert parsed["currency"] == "USDC"
        assert parsed["chain_id"] == 8453

    def test_run_returns_valid_json(self, tool, mock_client):
        result = tool._run()
        parsed = json.loads(result)
        assert isinstance(parsed, dict)
        assert len(parsed) == 5

    @pytest.mark.asyncio
    async def test_arun_calls_aget_balance(self, tool, mock_client):
        result = await tool._arun()
        mock_client.aget_balance.assert_called_once()
        parsed = json.loads(result)
        assert parsed["on_chain_balance"] == "50.000000"

    def test_run_propagates_errors(self, tool, mock_client):
        mock_client.get_balance.side_effect = Exception("API error")
        with pytest.raises(Exception, match="API error"):
            tool._run()

    @pytest.mark.asyncio
    async def test_arun_propagates_errors(self, tool, mock_client):
        mock_client.aget_balance = AsyncMock(side_effect=Exception("Timeout"))
        with pytest.raises(Exception, match="Timeout"):
            await tool._arun()
