import unittest
from unittest.mock import patch


from traia_tools.tools.coingecko_quote_tool.coingecko_quote_tool import (
    CoingeckoUniversalQuoteTool,
)


class _MockResponse:
    def __init__(self, payload: dict, status_code: int = 200):
        self._payload = payload
        self.status_code = status_code

    def raise_for_status(self):
        # Simulate requests.Response.raise_for_status() behavior.
        if 400 <= self.status_code:
            raise Exception(f"HTTP {self.status_code}")

    def json(self):
        return self._payload


class TestCoingeckoUniversalQuoteTool(unittest.TestCase):
    def test_schema_advertises_multi_token_inputs(self):
        schema = CoingeckoUniversalQuoteTool().args_schema
        self.assertEqual(
            set(schema.model_fields.keys()),
            {"token_id", "token_ids", "amount", "vs_currency"},
        )

    @patch("traia_tools.tools.coingecko_quote_tool.coingecko_quote_tool.requests.get")
    def test_single_token_quote_uses_amount_and_returns_value(self, mock_get):
        mock_get.return_value = _MockResponse({"bitcoin": {"usd": 100.0}})

        tool = CoingeckoUniversalQuoteTool()
        result = tool.run(token_id="bitcoin", amount=2.5)

        self.assertEqual(result["token"], "bitcoin")
        self.assertEqual(result["vs_currency"], "usd")
        self.assertEqual(result["price"], 100.0)
        self.assertEqual(result["amount"], 2.5)
        self.assertEqual(result["value"], 250.0)

        # Ensure we call the CoinGecko endpoint using params (multi-token compatible).
        args, kwargs = mock_get.call_args
        self.assertIn("/simple/price", args[0])
        self.assertEqual(kwargs["params"]["ids"], "bitcoin")
        self.assertEqual(kwargs["params"]["vs_currencies"], "usd")

    @patch("traia_tools.tools.coingecko_quote_tool.coingecko_quote_tool.requests.get")
    def test_multi_token_quote_accepts_list_and_reports_missing(self, mock_get):
        mock_get.return_value = _MockResponse({"bitcoin": {"usd": 10.0}})

        tool = CoingeckoUniversalQuoteTool()
        result = tool.run(token_ids=["bitcoin", "ethereum"], amount=3)

        self.assertEqual(result["vs_currency"], "usd")
        self.assertEqual([q["token"] for q in result["quotes"]], ["bitcoin", "ethereum"])

        btc = result["quotes"][0]
        eth = result["quotes"][1]

        self.assertEqual(btc["price"], 10.0)
        self.assertEqual(btc["value"], 30.0)

        self.assertIsNone(eth["price"])
        self.assertIsNone(eth["value"])
        self.assertEqual(result["missing_tokens"], ["ethereum"])

    @patch("traia_tools.tools.coingecko_quote_tool.coingecko_quote_tool.requests.get")
    def test_multi_token_quote_accepts_comma_separated_string(self, mock_get):
        mock_get.return_value = _MockResponse({"bitcoin": {"eur": 50.0}, "ethereum": {"eur": 25.0}})

        tool = CoingeckoUniversalQuoteTool()
        result = tool.run(token_ids="bitcoin, ethereum", vs_currency="EUR")

        self.assertEqual(result["vs_currency"], "eur")
        self.assertEqual([q["token"] for q in result["quotes"]], ["bitcoin", "ethereum"])
        self.assertEqual([q["price"] for q in result["quotes"]], [50.0, 25.0])

    def test_errors_when_no_tokens_provided(self):
        tool = CoingeckoUniversalQuoteTool()
        result = tool.run(token_id=None, token_ids=None)
        self.assertIn("Invalid input", result["error"])

    def test_errors_when_amount_invalid(self):
        tool = CoingeckoUniversalQuoteTool()
        result = tool.run(token_id="bitcoin", amount=0)
        self.assertIn("amount", result["error"])


if __name__ == "__main__":
    unittest.main()

