# tests/test_llm_adapter.py
"""Tests for the LLM adapter module."""

from unittest.mock import MagicMock, patch

import pytest

from crashsense.core.llm_adapter import LLMAdapter


class TestParseLLMText:
    """Test the static text parser."""

    def test_structured_sections(self):
        text = (
            "## Root Cause\n"
            "Insufficient token balance.\n\n"
            "## Explanation\n"
            "Your wallet has fewer tokens than needed.\n\n"
            "## Suggested Fix\n"
            "Buy more tokens on a DEX."
        )
        result = LLMAdapter._parse_llm_text(text)
        assert "Insufficient token balance" in result["root_cause"]
        assert "fewer tokens" in result["explanation"]
        assert "Buy more" in result["suggested_fix"]

    def test_fallback_plain_text(self):
        text = "Something went wrong with the transaction."
        result = LLMAdapter._parse_llm_text(text)
        assert result["explanation"] == text
        assert result["root_cause"] == "See explanation"

    def test_partial_sections(self):
        text = "## Root Cause\nGas limit too low."
        result = LLMAdapter._parse_llm_text(text)
        assert "Gas limit" in result["root_cause"]


class TestHeuristicAnswer:
    """Test the offline heuristic fallback."""

    def setup_method(self):
        self.adapter = LLMAdapter.__new__(LLMAdapter)
        self.adapter.provider = "heuristic"

    def test_out_of_gas(self):
        result = self.adapter._heuristic_answer("Transaction out of gas")
        assert "gas" in result["explanation"].lower()
        assert "[Heuristic]" in result["explanation"]

    def test_insufficient_balance(self):
        result = self.adapter._heuristic_answer("insufficient balance for transfer")
        assert "balance" in result["explanation"].lower()

    def test_allowance(self):
        result = self.adapter._heuristic_answer("insufficient allowance")
        assert "allowance" in result["explanation"].lower()

    def test_slippage(self):
        result = self.adapter._heuristic_answer("slippage exceeded on swap")
        assert "slippage" in result["explanation"].lower() or "expired" in result["explanation"].lower()

    def test_nonce(self):
        result = self.adapter._heuristic_answer("nonce too low")
        assert "nonce" in result["explanation"].lower()

    def test_unknown(self):
        result = self.adapter._heuristic_answer("some random text that matches nothing")
        assert "Unable to determine" in result["explanation"]


class TestListProviders:
    @patch("crashsense.core.llm_adapter.get_api_key")
    @patch("crashsense.core.llm_adapter.load_config")
    def test_returns_all_providers(self, mock_config, mock_key):
        mock_config.return_value = {
            "provider": "gemini",
            "gemini": {"model": "gemini-2.5-flash-preview-05-20"},
            "openai": {"model": "gpt-4o-mini"},
            "claude": {"model": "claude-sonnet-4-20250514"},
            "ollama": {"model": "llama3.2:1b", "host": "http://localhost:11434"},
        }
        mock_key.return_value = "fake_key"

        adapter = LLMAdapter(provider="gemini")
        providers = adapter.list_providers()
        names = {p["name"] for p in providers}
        assert names == {"gemini", "openai", "claude", "ollama"}
        # gemini should be active
        active = [p for p in providers if p["active"]]
        assert len(active) == 1
        assert active[0]["name"] == "gemini"


class TestErrorResult:
    def test_error_result_structure(self):
        result = LLMAdapter._error_result("Test error message")
        assert result["explanation"] == "Test error message"
        assert result["root_cause"] == "error"
        assert result["suggested_fix"] == ""
