"""
Tests for the ElasticsearchSearchEngine class.

Tests cover:
- Initialization and configuration
- Authentication options (basic auth, API key, cloud ID)
- Preview generation
- Full content retrieval
- Query string and DSL search methods
- Response processing
"""

from unittest.mock import Mock, patch, MagicMock
import pytest


class TestElasticsearchSearchEngineInit:
    """Tests for ElasticsearchSearchEngine initialization."""

    def test_init_with_defaults(self):
        """Initialize with default values."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test-cluster",
            "version": {"number": "8.0.0"},
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine()

            assert engine.max_results == 10
            assert engine.index_name == "documents"
            assert engine.highlight_fields == ["content", "title"]
            assert engine.search_fields == ["content", "title"]
            assert engine.filter_query == {}

    def test_init_with_custom_index(self):
        """Initialize with custom index name."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine(index_name="my_documents")

            assert engine.index_name == "my_documents"

    def test_init_with_custom_max_results(self):
        """Initialize with custom max_results."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine(max_results=50)

            assert engine.max_results == 50

    def test_init_with_basic_auth(self):
        """Initialize with basic authentication."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ) as mock_es:
            ElasticsearchSearchEngine(username="user", password="pass")

            call_kwargs = mock_es.call_args[1]
            assert call_kwargs["basic_auth"] == ("user", "pass")

    def test_init_with_api_key(self):
        """Initialize with API key authentication."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ) as mock_es:
            ElasticsearchSearchEngine(api_key="test-api-key")

            call_kwargs = mock_es.call_args[1]
            assert call_kwargs["api_key"] == "test-api-key"

    def test_init_with_cloud_id(self):
        """Initialize with Elastic Cloud ID."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ) as mock_es:
            ElasticsearchSearchEngine(cloud_id="test-cloud-id")

            call_kwargs = mock_es.call_args[1]
            assert call_kwargs["cloud_id"] == "test-cloud-id"

    def test_init_with_filter_query(self):
        """Initialize with filter query."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }

        filter_q = {"term": {"status": "published"}}

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine(filter_query=filter_q)

            assert engine.filter_query == filter_q

    def test_init_with_custom_fields(self):
        """Initialize with custom highlight and search fields."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine(
                highlight_fields=["body", "summary"],
                search_fields=["body", "summary", "tags"],
            )

            assert engine.highlight_fields == ["body", "summary"]
            assert engine.search_fields == ["body", "summary", "tags"]

    def test_init_with_llm(self):
        """Initialize with LLM."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }
        mock_llm = Mock()

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine(llm=mock_llm)

            assert engine.llm is mock_llm

    def test_init_normalizes_json_string_hosts(self):
        """JSON-encoded string hosts are normalized to a list."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ) as mock_es:
            ElasticsearchSearchEngine(
                hosts='["http://localhost:9200"]',
            )

            # Elasticsearch client should receive a list, not a string
            mock_es.assert_called_once()
            call_args = mock_es.call_args[0]
            assert call_args[0] == ["http://localhost:9200"]

    def test_init_normalizes_json_string_fields(self):
        """JSON-encoded string fields are normalized to lists."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine(
                highlight_fields='["body", "summary"]',
                search_fields='["body", "summary", "tags"]',
            )

            assert engine.highlight_fields == ["body", "summary"]
            assert engine.search_fields == ["body", "summary", "tags"]

    def test_init_connection_failure(self):
        """Initialize handles connection failure."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.side_effect = Exception("Connection refused")

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            with pytest.raises(ConnectionError) as exc_info:
                ElasticsearchSearchEngine()

            assert "Could not connect to Elasticsearch" in str(exc_info.value)


class TestGetPreviews:
    """Tests for _get_previews method."""

    def test_get_previews_returns_results(self):
        """Get previews returns formatted results."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }
        mock_client.search.return_value = {
            "hits": {
                "hits": [
                    {
                        "_id": "doc1",
                        "_index": "documents",
                        "_score": 1.5,
                        "_source": {
                            "title": "Test Document",
                            "content": "This is the document content.",
                            "url": "https://example.com/doc1",
                        },
                        "highlight": {
                            "content": [
                                "This is the <em>document</em> content."
                            ],
                        },
                    }
                ]
            }
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine()
            previews = engine._get_previews("document")

            assert len(previews) == 1
            assert previews[0]["id"] == "doc1"
            assert previews[0]["title"] == "Test Document"
            assert previews[0]["link"] == "https://example.com/doc1"
            assert previews[0]["score"] == 1.5

    def test_get_previews_with_highlights(self):
        """Get previews extracts highlighted snippets."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }
        mock_client.search.return_value = {
            "hits": {
                "hits": [
                    {
                        "_id": "doc1",
                        "_index": "documents",
                        "_score": 1.0,
                        "_source": {
                            "title": "Test",
                            "content": "Full content here",
                        },
                        "highlight": {
                            "title": ["<em>Test</em> title"],
                            "content": ["Matched <em>content</em> here"],
                        },
                    }
                ]
            }
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine()
            previews = engine._get_previews("test")

            assert "Test" in previews[0]["snippet"]
            assert "content" in previews[0]["snippet"]

    def test_get_previews_no_highlights_fallback(self):
        """Get previews falls back to content when no highlights."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }
        mock_client.search.return_value = {
            "hits": {
                "hits": [
                    {
                        "_id": "doc1",
                        "_index": "documents",
                        "_score": 1.0,
                        "_source": {
                            "title": "Test",
                            "content": "This is the fallback content that should be used.",
                        },
                        "highlight": {},
                    }
                ]
            }
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine()
            previews = engine._get_previews("test")

            assert "fallback content" in previews[0]["snippet"]

    def test_get_previews_no_url_fallback(self):
        """Get previews creates elasticsearch:// URL when no URL in source."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }
        mock_client.search.return_value = {
            "hits": {
                "hits": [
                    {
                        "_id": "doc123",
                        "_index": "documents",
                        "_score": 1.0,
                        "_source": {"title": "Test", "content": "Content"},
                        "highlight": {},
                    }
                ]
            }
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine()
            previews = engine._get_previews("test")

            assert "elasticsearch://documents/doc123" in previews[0]["link"]

    def test_get_previews_with_filter_query(self):
        """Get previews includes filter query."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }
        mock_client.search.return_value = {"hits": {"hits": []}}

        filter_q = {"term": {"status": "published"}}

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine(filter_query=filter_q)
            engine._get_previews("test")

            call_kwargs = mock_client.search.call_args[1]
            body = call_kwargs["body"]
            assert "bool" in body["query"]
            assert "filter" in body["query"]["bool"]

    def test_get_previews_empty_results(self):
        """Get previews handles empty results."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }
        mock_client.search.return_value = {"hits": {"hits": []}}

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine()
            previews = engine._get_previews("test")

            assert previews == []

    def test_get_previews_exception(self):
        """Get previews handles exceptions gracefully."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }
        mock_client.search.side_effect = Exception("Search error")

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine()
            previews = engine._get_previews("test")

            assert previews == []


class TestGetFullContent:
    """Tests for _get_full_content method."""

    def test_get_full_content_returns_items(self):
        """Get full content fetches full document."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }
        mock_client.get.return_value = {
            "_source": {
                "content": "This is the full document content with all the details.",
                "title": "Test Document",
                "author": "Test Author",
            }
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine()
            items = [
                {
                    "id": "doc1",
                    "title": "Test",
                    "snippet": "Short snippet",
                }
            ]
            results = engine._get_full_content(items)

            assert len(results) == 1
            assert "full document content" in results[0]["content"]
            assert results[0]["author"] == "Test Author"

    def test_get_full_content_skips_items_without_id(self):
        """Get full content skips items without document ID."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine()
            items = [
                {"title": "No ID Document", "snippet": "Snippet"}  # No 'id' key
            ]
            results = engine._get_full_content(items)

            assert len(results) == 1
            mock_client.get.assert_not_called()

    def test_get_full_content_handles_fetch_error(self):
        """Get full content handles document fetch errors."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }
        mock_client.get.side_effect = Exception("Document not found")

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine()
            items = [{"id": "doc1", "title": "Test", "snippet": "Snippet"}]
            results = engine._get_full_content(items)

            assert len(results) == 1
            # Should still have the original data
            assert results[0]["title"] == "Test"

    def test_get_full_content_snippets_only_mode(self):
        """Get full content respects snippets-only mode."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            with patch(
                "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.search_config"
            ) as mock_config:
                mock_config.SEARCH_SNIPPETS_ONLY = True
                engine = ElasticsearchSearchEngine()
                items = [{"id": "doc1", "title": "Test", "snippet": "Snippet"}]
                results = engine._get_full_content(items)

                # Should return items unchanged
                assert results == items
                mock_client.get.assert_not_called()


class TestSearchByQueryString:
    """Tests for search_by_query_string method."""

    def test_search_by_query_string(self):
        """Search by query string syntax."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }
        mock_client.search.return_value = {
            "hits": {
                "hits": [
                    {
                        "_id": "doc1",
                        "_index": "documents",
                        "_score": 1.0,
                        "_source": {"title": "Test", "content": "Content"},
                        "highlight": {},
                    }
                ]
            }
        }
        mock_client.get.return_value = {"_source": {"content": "Full content"}}

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine()
            results = engine.search_by_query_string(
                'title:"test" AND content:example'
            )

            assert len(results) == 1
            call_kwargs = mock_client.search.call_args[1]
            assert "query_string" in call_kwargs["body"]["query"]

    def test_search_by_query_string_exception(self):
        """Search by query string handles exceptions."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }
        mock_client.search.side_effect = Exception("Query error")

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine()
            results = engine.search_by_query_string("invalid:query")

            assert results == []


class TestSearchByDSL:
    """Tests for search_by_dsl method."""

    def test_search_by_dsl(self):
        """Search by Elasticsearch DSL."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }
        mock_client.search.return_value = {
            "hits": {
                "hits": [
                    {
                        "_id": "doc1",
                        "_index": "documents",
                        "_score": 1.0,
                        "_source": {"title": "Test", "content": "Content"},
                        "highlight": {},
                    }
                ]
            }
        }
        mock_client.get.return_value = {"_source": {"content": "Full content"}}

        dsl_query = {
            "query": {"match": {"content": "test"}},
            "size": 5,
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine()
            results = engine.search_by_dsl(dsl_query)

            assert len(results) == 1
            mock_client.search.assert_called_with(
                index="documents",
                body=dsl_query,
            )

    def test_search_by_dsl_exception(self):
        """Search by DSL handles exceptions."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }
        mock_client.search.side_effect = Exception("DSL error")

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine()
            results = engine.search_by_dsl({"query": {}})

            assert results == []


class TestProcessEsResponse:
    """Tests for _process_es_response method."""

    def test_process_es_response(self):
        """Process Elasticsearch response."""
        from local_deep_research.web_search_engines.engines.search_engine_elasticsearch import (
            ElasticsearchSearchEngine,
        )

        mock_client = MagicMock()
        mock_client.info.return_value = {
            "cluster_name": "test",
            "version": {"number": "8.0"},
        }

        response = {
            "hits": {
                "hits": [
                    {
                        "_id": "doc1",
                        "_index": "test_index",
                        "_score": 2.5,
                        "_source": {
                            "title": "Document Title",
                            "content": "Document content here",
                            "url": "https://example.com",
                        },
                        "highlight": {
                            "content": ["Matched <em>content</em> here"],
                        },
                    },
                    {
                        "_id": "doc2",
                        "_index": "test_index",
                        "_score": 1.5,
                        "_source": {"title": "Another Doc"},
                        "highlight": {},
                    },
                ]
            }
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_elasticsearch.Elasticsearch",
            return_value=mock_client,
        ):
            engine = ElasticsearchSearchEngine()
            previews = engine._process_es_response(response)

            assert len(previews) == 2
            assert previews[0]["id"] == "doc1"
            assert previews[0]["score"] == 2.5
            assert "content" in previews[0]["snippet"]
            assert previews[1]["title"] == "Another Doc"
