"""
Tests for the PubChem search engine.
Tests initialization, search functionality, and error handling.
"""

import pytest
from unittest.mock import Mock


class TestPubChemSearchEngineInit:
    """Tests for PubChem search engine initialization."""

    def test_init_default_parameters(self):
        """Test initialization with default parameters."""
        from local_deep_research.web_search_engines.engines.search_engine_pubchem import (
            PubChemSearchEngine,
        )

        engine = PubChemSearchEngine()

        assert engine.max_results == 10
        assert engine.include_synonyms is True
        assert engine.is_public is True
        assert engine.is_scientific is True

    def test_init_custom_parameters(self):
        """Test initialization with custom parameters."""
        from local_deep_research.web_search_engines.engines.search_engine_pubchem import (
            PubChemSearchEngine,
        )

        engine = PubChemSearchEngine(
            max_results=20,
            include_synonyms=False,
        )

        assert engine.max_results == 20
        assert engine.include_synonyms is False

    def test_base_url_set(self):
        """Test that API base URL is correctly set."""
        from local_deep_research.web_search_engines.engines.search_engine_pubchem import (
            PubChemSearchEngine,
        )

        engine = PubChemSearchEngine()
        assert engine.base_url == "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
        assert (
            engine.autocomplete_url
            == "https://pubchem.ncbi.nlm.nih.gov/rest/autocomplete"
        )

    def test_user_agent_header_set(self):
        """Test that User-Agent header is set."""
        from local_deep_research.web_search_engines.engines.search_engine_pubchem import (
            PubChemSearchEngine,
        )

        engine = PubChemSearchEngine()
        assert "User-Agent" in engine.headers
        assert "LocalDeepResearch" in engine.headers["User-Agent"]


class TestPubChemSearchExecution:
    """Tests for PubChem search execution."""

    @pytest.fixture
    def engine(self):
        """Create a PubChem engine."""
        from local_deep_research.web_search_engines.engines.search_engine_pubchem import (
            PubChemSearchEngine,
        )

        return PubChemSearchEngine(max_results=5)

    def test_get_previews_handles_exception(self, engine, monkeypatch):
        """Test that exceptions are handled gracefully."""
        import requests

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubchem.safe_get",
            Mock(side_effect=requests.RequestException("Network error")),
        )

        previews = engine._get_previews("aspirin")
        assert previews == []


class TestPubChemErrorHandling:
    """Tests for PubChem error handling scenarios."""

    @pytest.fixture
    def engine(self):
        """Create a PubChem engine."""
        from local_deep_research.web_search_engines.engines.search_engine_pubchem import (
            PubChemSearchEngine,
        )

        return PubChemSearchEngine(max_results=5)

    def test_get_previews_handles_autocomplete_failure(
        self, engine, monkeypatch
    ):
        """Test that autocomplete returns results but direct lookup fails."""
        # Autocomplete returns a compound name, but the CID lookup fails
        mock_autocomplete = Mock()
        mock_autocomplete.status_code = 200
        mock_autocomplete.json = Mock(
            return_value={"dictionary_terms": {"compound": ["test compound"]}}
        )
        mock_autocomplete.raise_for_status = Mock()

        # CID lookup returns 404 (compound not found)
        mock_not_found = Mock()
        mock_not_found.status_code = 404

        def mock_safe_get(*args, **kwargs):
            if "autocomplete" in args[0]:
                return mock_autocomplete
            return mock_not_found

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubchem.safe_get",
            mock_safe_get,
        )

        # Should return empty list since compound lookup fails
        previews = engine._get_previews("test compound")
        assert previews == []

    def test_get_previews_handles_not_found_compound(self, engine, monkeypatch):
        """Test handling of 404 status code when compound is not found."""

        # First call for autocomplete returns empty (to trigger direct lookup path)
        mock_autocomplete = Mock()
        mock_autocomplete.status_code = 200
        mock_autocomplete.json = Mock(
            return_value={"dictionary_terms": {"compound": []}}
        )
        mock_autocomplete.raise_for_status = Mock()

        # Second call for direct lookup returns 404 (compound not found)
        mock_not_found = Mock()
        mock_not_found.status_code = 404

        call_count = [0]

        def mock_safe_get(*args, **kwargs):
            call_count[0] += 1
            if call_count[0] == 1:
                return mock_autocomplete
            return mock_not_found

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubchem.safe_get",
            mock_safe_get,
        )

        # Should return empty list since no compound found
        previews = engine._get_previews("nonexistent")
        assert previews == []

    def test_get_previews_raises_rate_limit_on_429(self, engine, monkeypatch):
        """Test that 429 status code raises RateLimitError."""
        from local_deep_research.web_search_engines.rate_limiting import (
            RateLimitError,
        )

        mock_response = Mock()
        mock_response.status_code = 429

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubchem.safe_get",
            Mock(return_value=mock_response),
        )

        with pytest.raises(RateLimitError):
            engine._get_previews("aspirin")

    def test_get_previews_empty_autocomplete_results(self, engine, monkeypatch):
        """Test handling when autocomplete returns no results."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json = Mock(
            return_value={"dictionary_terms": {"compound": []}}
        )
        mock_response.raise_for_status = Mock()

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubchem.safe_get",
            Mock(return_value=mock_response),
        )

        previews = engine._get_previews("xyznonexistent12345")
        assert previews == []

    def test_get_previews_http_500_error(self, engine, monkeypatch):
        """Test handling of HTTP 500 server error."""
        import requests

        mock_response = Mock()
        mock_response.status_code = 500
        mock_response.raise_for_status = Mock(
            side_effect=requests.HTTPError("500 Server Error")
        )

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubchem.safe_get",
            Mock(return_value=mock_response),
        )

        previews = engine._get_previews("test")
        assert previews == []

    def test_get_previews_malformed_json(self, engine, monkeypatch):
        """Test handling of malformed JSON response."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json = Mock(side_effect=ValueError("Invalid JSON"))

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubchem.safe_get",
            Mock(return_value=mock_response),
        )

        previews = engine._get_previews("test")
        assert previews == []

    def test_compound_with_missing_properties(self, engine, monkeypatch):
        """Test handling of compound with missing properties."""
        # Mock for autocomplete
        mock_autocomplete = Mock()
        mock_autocomplete.status_code = 200
        mock_autocomplete.json = Mock(
            return_value={"dictionary_terms": {"compound": ["test compound"]}}
        )
        mock_autocomplete.raise_for_status = Mock()

        # Mock for compound CID lookup
        mock_compound = Mock()
        mock_compound.status_code = 200
        mock_compound.json = Mock(
            return_value={"IdentifierList": {"CID": [12345]}}
        )
        mock_compound.raise_for_status = Mock()

        # Mock for properties - empty
        mock_properties = Mock()
        mock_properties.status_code = 200
        mock_properties.json = Mock(
            return_value={"PropertyTable": {"Properties": [{}]}}
        )
        mock_properties.raise_for_status = Mock()

        # Mock for description - empty
        mock_description = Mock()
        mock_description.status_code = 200
        mock_description.json = Mock(
            return_value={"InformationList": {"Information": []}}
        )
        mock_description.raise_for_status = Mock()

        def mock_safe_get(*args, **kwargs):
            url = args[0]
            if "autocomplete" in url:
                return mock_autocomplete
            if "property" in url:
                return mock_properties
            if "description" in url:
                return mock_description
            return mock_compound

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubchem.safe_get",
            mock_safe_get,
        )

        previews = engine._get_previews("test compound")
        # Should not crash, should return result with empty fields
        assert isinstance(previews, list)
        assert len(previews) == 1
        assert previews[0]["cid"] == 12345


class TestPubChemFullContent:
    """Tests for PubChem full content retrieval."""

    def test_get_full_content_builds_content(self):
        """Test that full content builds proper content string."""
        from local_deep_research.web_search_engines.engines.search_engine_pubchem import (
            PubChemSearchEngine,
        )

        engine = PubChemSearchEngine(include_synonyms=False)

        items = [
            {
                "id": "2244",
                "cid": 2244,
                "title": "Aspirin",
                "molecular_formula": "C9H8O4",
                "molecular_weight": "180.16",
                "iupac_name": "2-acetyloxybenzoic acid",
                "smiles": "CC(=O)OC1=CC=CC=C1C(=O)O",
                "inchi_key": "BSYNRYMUTXBXSQ-UHFFFAOYSA-N",
                "description": "Aspirin is an anti-inflammatory drug.",
                "_raw": {
                    "properties": {
                        "XLogP": 1.2,
                        "TPSA": 63.6,
                        "HBondDonorCount": 1,
                        "HBondAcceptorCount": 4,
                    },
                    "description": "Aspirin is an anti-inflammatory drug.",
                },
            }
        ]

        results = engine._get_full_content(items)

        assert len(results) == 1
        assert "Compound: Aspirin" in results[0]["content"]
        assert "CID: 2244" in results[0]["content"]
        assert "Molecular Formula: C9H8O4" in results[0]["content"]
        assert "Molecular Weight: 180.16 g/mol" in results[0]["content"]
        assert "_raw" not in results[0]


@pytest.mark.integration
class TestPubChemIntegration:
    """Integration tests making real API calls to PubChem."""

    def test_real_search_aspirin(self):
        """Test real search for aspirin."""
        from local_deep_research.web_search_engines.engines.search_engine_pubchem import (
            PubChemSearchEngine,
        )

        engine = PubChemSearchEngine(max_results=3)
        results = engine._get_previews("aspirin")

        # Verify we got results
        assert len(results) > 0, "Should find aspirin"

        # Verify first result
        first = results[0]
        assert "title" in first, "Should have title"
        assert "link" in first, "Should have link"
        assert first["source"] == "PubChem", "Source should be PubChem"
        assert "pubchem.ncbi.nlm.nih.gov" in first["link"], (
            "Link should be PubChem URL"
        )

    def test_real_search_returns_molecular_formula(self):
        """Test that real search returns molecular formula."""
        from local_deep_research.web_search_engines.engines.search_engine_pubchem import (
            PubChemSearchEngine,
        )

        engine = PubChemSearchEngine(max_results=1)
        results = engine._get_previews("caffeine")

        assert len(results) > 0, "Should find caffeine"

        first = results[0]
        assert first.get("molecular_formula"), "Should have molecular_formula"
        # Caffeine is C8H10N4O2
        assert "C" in first["molecular_formula"], "Formula should contain C"

    def test_real_search_returns_molecular_weight(self):
        """Test that real search returns molecular weight."""
        from local_deep_research.web_search_engines.engines.search_engine_pubchem import (
            PubChemSearchEngine,
        )

        engine = PubChemSearchEngine(max_results=1)
        results = engine._get_previews("glucose")

        assert len(results) > 0, "Should find glucose"

        first = results[0]
        assert first.get("molecular_weight"), "Should have molecular_weight"
        # Just verify molecular weight is a positive number
        # (PubChem may return different glucose compounds like derivatives)
        weight = float(first["molecular_weight"])
        assert weight > 0, f"Molecular weight should be positive, got {weight}"

    def test_real_search_returns_smiles(self):
        """Test that real search returns SMILES notation."""
        from local_deep_research.web_search_engines.engines.search_engine_pubchem import (
            PubChemSearchEngine,
        )

        engine = PubChemSearchEngine(max_results=1)
        results = engine._get_previews("ethanol")

        assert len(results) > 0, "Should find ethanol"

        first = results[0]
        assert first.get("smiles"), "Should have SMILES"
        # Ethanol SMILES should contain C and O
        assert "C" in first["smiles"] and "O" in first["smiles"], (
            f"Ethanol SMILES should contain C and O, got: {first['smiles']}"
        )

    def test_real_search_returns_cid(self):
        """Test that real search returns compound ID."""
        from local_deep_research.web_search_engines.engines.search_engine_pubchem import (
            PubChemSearchEngine,
        )

        engine = PubChemSearchEngine(max_results=1)
        results = engine._get_previews("water")

        assert len(results) > 0, "Should find water"

        first = results[0]
        assert first.get("cid"), "Should have CID"
        assert isinstance(first["cid"], int), "CID should be integer"

    def test_real_search_ibuprofen(self):
        """Test real search for ibuprofen."""
        from local_deep_research.web_search_engines.engines.search_engine_pubchem import (
            PubChemSearchEngine,
        )

        engine = PubChemSearchEngine(max_results=1)
        results = engine._get_previews("ibuprofen")

        assert len(results) > 0, "Should find ibuprofen"

        first = results[0]
        # Ibuprofen CID is 3672
        assert first.get("cid"), "Should have CID"
        assert first.get("iupac_name"), "Should have IUPAC name"

    def test_real_search_partial_name(self):
        """Test real search with partial compound name."""
        from local_deep_research.web_search_engines.engines.search_engine_pubchem import (
            PubChemSearchEngine,
        )

        engine = PubChemSearchEngine(max_results=5)
        results = engine._get_previews("acet")

        # Should find compounds starting with "acet" like acetone, acetamide, etc.
        assert len(results) > 0, "Should find compounds starting with 'acet'"

        # At least one should have a title starting with "acet"
        titles = [r["title"].lower() for r in results]
        has_acet = any("acet" in t for t in titles)
        assert has_acet, (
            f"Should find compound starting with 'acet', got: {titles}"
        )
