"""
Coverage tests for es_utils.ElasticsearchManager.

Focuses on paths not exercised by the existing test_es_utils.py:
- close() delegates to safe_close
- create_index with no mappings / no settings (default generation paths)
- index_document with refresh=False (default) vs True
- bulk_index_documents with id_field but field absent from doc
- search without highlight (highlight=False branch already covered; add coverage
  for custom multi_match type expectation)
- index_file with explicit title_field=None
- index_directory with multiple glob patterns
"""

from unittest.mock import Mock, patch

import pytest

MODULE = "local_deep_research.utilities.es_utils"


# ---------------------------------------------------------------------------
# Shared fixture
# ---------------------------------------------------------------------------


def _make_manager():
    """Return an ElasticsearchManager with a fully mocked ES client."""
    mock_client = Mock()
    mock_client.info.return_value = {
        "cluster_name": "test-cluster",
        "version": {"number": "8.0.0"},
    }
    with patch(f"{MODULE}.Elasticsearch", return_value=mock_client):
        from local_deep_research.utilities.es_utils import ElasticsearchManager

        mgr = ElasticsearchManager()
    return mgr


# ---------------------------------------------------------------------------
# close()
# ---------------------------------------------------------------------------


class TestClose:
    def test_close_delegates_to_safe_close(self):
        mgr = _make_manager()
        with patch(
            "local_deep_research.utilities.resource_utils.safe_close"
        ) as mock_safe_close:
            mgr.close()
            mock_safe_close.assert_called_once()


# ---------------------------------------------------------------------------
# create_index – default mappings / settings paths
# ---------------------------------------------------------------------------


class TestCreateIndexDefaults:
    def test_creates_default_mappings_when_none_provided(self):
        mgr = _make_manager()
        mgr.client.indices.exists.return_value = False
        mgr.client.indices.create.return_value = {"acknowledged": True}

        result = mgr.create_index("my-index")

        assert result is True
        call_kwargs = mgr.client.indices.create.call_args[1]
        # Default mappings should contain "properties"
        assert "properties" in call_kwargs["mappings"]

    def test_creates_default_settings_when_none_provided(self):
        mgr = _make_manager()
        mgr.client.indices.exists.return_value = False
        mgr.client.indices.create.return_value = {"acknowledged": True}

        mgr.create_index("my-index")

        call_kwargs = mgr.client.indices.create.call_args[1]
        assert "number_of_shards" in call_kwargs["settings"]

    def test_custom_mappings_bypass_defaults(self):
        mgr = _make_manager()
        mgr.client.indices.exists.return_value = False
        mgr.client.indices.create.return_value = {"acknowledged": True}

        custom = {"properties": {"field_x": {"type": "keyword"}}}
        mgr.create_index("idx", mappings=custom)

        call_kwargs = mgr.client.indices.create.call_args[1]
        assert call_kwargs["mappings"] == custom

    def test_custom_settings_bypass_defaults(self):
        mgr = _make_manager()
        mgr.client.indices.exists.return_value = False
        mgr.client.indices.create.return_value = {"acknowledged": True}

        custom_settings = {"number_of_shards": 5, "number_of_replicas": 1}
        mgr.create_index("idx", settings=custom_settings)

        call_kwargs = mgr.client.indices.create.call_args[1]
        assert call_kwargs["settings"] == custom_settings


# ---------------------------------------------------------------------------
# index_document – refresh variants and document_id=None
# ---------------------------------------------------------------------------


class TestIndexDocumentVariants:
    def test_document_id_none_passes_none_to_client(self):
        mgr = _make_manager()
        mgr.client.index.return_value = {"_id": "auto-generated-id"}

        result = mgr.index_document("idx", {"title": "doc"}, document_id=None)

        assert result == "auto-generated-id"
        call_kwargs = mgr.client.index.call_args[1]
        assert call_kwargs["id"] is None

    def test_refresh_false_is_default(self):
        mgr = _make_manager()
        mgr.client.index.return_value = {"_id": "abc"}

        mgr.index_document("idx", {"title": "doc"})

        call_kwargs = mgr.client.index.call_args[1]
        assert call_kwargs["refresh"] is False


# ---------------------------------------------------------------------------
# bulk_index_documents – id_field absent from doc
# ---------------------------------------------------------------------------


class TestBulkIndexIdFieldAbsent:
    def test_missing_id_field_docs_omit_id(self):
        mgr = _make_manager()

        with patch(f"{MODULE}.bulk", return_value=(2, 0)) as mock_bulk:
            docs = [{"title": "no-id-field-here"}, {"title": "also-no-id"}]
            result = mgr.bulk_index_documents("idx", docs, id_field="doc_id")

        assert result == 2
        actions = mock_bulk.call_args[0][1]
        for action in actions:
            assert "_id" not in action


# ---------------------------------------------------------------------------
# search – query structure
# ---------------------------------------------------------------------------


class TestSearchQueryStructure:
    def test_search_uses_best_fields_type(self):
        mgr = _make_manager()
        mgr.client.search.return_value = {"hits": {"hits": []}}

        mgr.search("idx", "quantum computing")

        call_kwargs = mgr.client.search.call_args[1]
        body = call_kwargs["body"]
        assert body["query"]["multi_match"]["type"] == "best_fields"

    def test_search_uses_tie_breaker(self):
        mgr = _make_manager()
        mgr.client.search.return_value = {"hits": {"hits": []}}

        mgr.search("idx", "test")

        body = mgr.client.search.call_args[1]["body"]
        assert body["query"]["multi_match"]["tie_breaker"] == pytest.approx(0.3)

    def test_search_highlight_uses_em_tags(self):
        mgr = _make_manager()
        mgr.client.search.return_value = {"hits": {"hits": []}}

        mgr.search("idx", "test", highlight=True)

        body = mgr.client.search.call_args[1]["body"]
        assert body["highlight"]["pre_tags"] == ["<em>"]
        assert body["highlight"]["post_tags"] == ["</em>"]


# ---------------------------------------------------------------------------
# index_file – title_field=None branch
# ---------------------------------------------------------------------------


class TestIndexFileTitleFieldNone:
    def test_title_field_none_excludes_title_from_doc(self, tmp_path):
        mgr = _make_manager()

        mock_doc = Mock()
        mock_doc.page_content = "file content"
        mock_doc.metadata = {"source": "test"}

        mock_loader = Mock()
        mock_loader.load.return_value = [mock_doc]
        mock_loader_class = Mock(return_value=mock_loader)

        indexed_doc = {}

        def _capture_index(index_name, document, refresh=False):
            indexed_doc.update(document)
            return "doc-id"

        mgr.index_document = _capture_index

        with patch(
            "langchain_community.document_loaders.UnstructuredFileLoader",
            mock_loader_class,
        ):
            mgr.index_file(
                "idx",
                str(tmp_path / "report.txt"),
                title_field=None,
            )

        # title should not be in the document when title_field is None
        assert "title" not in indexed_doc
