# tests/test_sdk.py
"""Tests for the CrashSense SDK (high-level public API)."""

from unittest.mock import MagicMock, patch

import pytest

from crashsense.sdk import CrashSense


@pytest.fixture
def cs(tmp_path):
    """Create a CrashSense SDK instance backed by a temp database."""
    db_path = str(tmp_path / "test_sdk.db")
    with patch("crashsense.sdk.load_config") as mock_cfg:
        mock_cfg.return_value = {
            "provider": "gemini",
            "chain": {"name": "ethereum"},
            "memory": {"path": db_path},
            "rag": {"enabled": False},
        }
        instance = CrashSense(provider="gemini", chain="ethereum", db_path=db_path)
    return instance


class TestSDKInit:
    def test_defaults(self, cs):
        assert cs.provider == "gemini"
        assert cs.chain == "ethereum"
        assert cs.auto_save is True

    def test_supported_chains(self):
        chains = CrashSense.supported_chains()
        assert "ethereum" in chains
        assert "polygon" in chains

    def test_supported_providers(self):
        providers = CrashSense.supported_providers()
        assert "gemini" in providers
        assert "openai" in providers
        assert "claude" in providers
        assert "ollama" in providers


class TestSDKHistory:
    def test_empty_history(self, cs):
        result = cs.history()
        assert result["total"] == 0
        assert result["items"] == []

    def test_history_after_manual_save(self, cs):
        # Manually save via the underlying store
        cs._store.save_diagnosis(
            tx_hash="0x" + "a" * 64,
            chain="ethereum",
            summary="Test",
            root_cause="Test",
            suggested_fix="Test",
        )
        result = cs.history()
        assert result["total"] == 1
        assert result["items"][0]["tx_hash"] == "0x" + "a" * 64

    def test_get_diagnosis(self, cs):
        cs._store.save_diagnosis(
            tx_hash="0x" + "b" * 64,
            chain="polygon",
            summary="Poly test",
            root_cause="LP",
            suggested_fix="Add liquidity",
        )
        d = cs.get_diagnosis("0x" + "b" * 64)
        assert d is not None
        assert d["chain"] == "polygon"

    def test_get_diagnosis_missing(self, cs):
        assert cs.get_diagnosis("0x" + "f" * 64) is None

    def test_search(self, cs):
        cs._store.save_diagnosis("0x" + "c" * 64, "ethereum", "slippage exceeded", "slippage", "increase")
        cs._store.save_diagnosis("0x" + "d" * 64, "ethereum", "gas limit", "gas", "increase gas")
        results = cs.search("slippage")
        assert len(results) == 1

    def test_stats(self, cs):
        cs._store.save_diagnosis("0x" + "e" * 64, "ethereum", "s", "r", "f", provider="gemini", status="failed")
        stats = cs.stats()
        assert stats["total_diagnoses"] == 1

    def test_get_actions(self, cs):
        rec = cs._store.save_diagnosis("0x" + "1" * 64, summary="t", root_cause="t", suggested_fix="t")
        cs._store.log_action(rec.id, "llm_call", "test call")
        actions = cs.get_actions(rec.id)
        assert len(actions) == 1
        assert actions[0]["action_type"] == "llm_call"


class TestSDKDiagnose:
    @patch("crashsense.sdk.TxAnalyzer")
    @patch("crashsense.sdk.RemediationEngine")
    def test_diagnose_saves_to_db(self, MockRemed, MockAnalyzer, cs):
        mock_analyzer = MockAnalyzer.return_value
        mock_analyzer.diagnose.return_value = {
            "tx_context": {
                "chain": "ethereum",
                "from": "0xSender",
                "to": "0xReceiver",
                "status": "failed",
                "function_called": "transfer",
                "gas_used": 50000,
                "gas_limit": 100000,
                "value_eth": 0.5,
                "revert_reason": "ERC20: insufficient balance",
            },
            "patterns_matched": ["INSUFFICIENT_BALANCE"],
            "analysis": {
                "explanation": "Balance too low",
                "root_cause": "Insufficient balance",
                "suggested_fix": "Fund your wallet",
            },
        }
        mock_remed = MockRemed.return_value
        mock_remed.get_remediation.return_value = [
            {"pattern": "INSUFFICIENT_BALANCE", "title": "Fund wallet"}
        ]

        tx = "0x" + "a" * 64
        result = cs.diagnose(tx)

        assert "analysis" in result
        assert "remediation" in result
        assert result["analysis"]["root_cause"] == "Insufficient balance"

        # Check persisted
        saved = cs.get_diagnosis(tx)
        assert saved is not None
        assert saved["root_cause"] == "Insufficient balance"
        assert saved["patterns"] == ["INSUFFICIENT_BALANCE"]

    @patch("crashsense.sdk.TxAnalyzer")
    @patch("crashsense.sdk.RemediationEngine")
    def test_diagnose_no_save(self, MockRemed, MockAnalyzer, cs):
        cs.auto_save = False
        mock_analyzer = MockAnalyzer.return_value
        mock_analyzer.diagnose.return_value = {
            "tx_context": {"chain": "ethereum"},
            "patterns_matched": [],
            "analysis": {"explanation": "OK", "root_cause": "none", "suggested_fix": ""},
        }
        mock_remed = MockRemed.return_value
        mock_remed.get_remediation.return_value = []

        result = cs.diagnose("0x" + "b" * 64)
        assert result["analysis"]["explanation"] == "OK"
        # Not saved
        assert cs.get_diagnosis("0x" + "b" * 64) is None

    @patch("crashsense.sdk.TxAnalyzer")
    @patch("crashsense.sdk.RemediationEngine")
    def test_diagnose_raw(self, MockRemed, MockAnalyzer, cs):
        mock_analyzer = MockAnalyzer.return_value
        mock_analyzer.diagnose_raw.return_value = {
            "raw_input": "execution reverted",
            "patterns_matched": ["EXECUTION_REVERTED"],
            "analysis": {
                "explanation": "Contract reverted the call",
                "root_cause": "Revert",
                "suggested_fix": "Check params",
            },
        }
        mock_remed = MockRemed.return_value
        mock_remed.get_remediation.return_value = []

        result = cs.diagnose_raw("execution reverted")
        assert "EXECUTION_REVERTED" in result["patterns_matched"]
