"""
Tests for uncovered code paths in ZenodoSearchEngine.

Targets:
- get_record: success, rate limit, exception
- search_datasets / search_software: resource_type switching, cleanup on exception
- _get_full_content: files info, references, related_identifiers, content building
- _get_previews: record parsing exception continues loop, license_info handling
"""

from unittest.mock import Mock, patch

import pytest

from local_deep_research.web_search_engines.engines.search_engine_zenodo import (
    ZenodoSearchEngine,
)
from local_deep_research.web_search_engines.rate_limiting import RateLimitError

MODULE = "local_deep_research.web_search_engines.engines.search_engine_zenodo"


@pytest.fixture
def engine():
    eng = ZenodoSearchEngine(max_results=5)
    eng.rate_tracker = Mock()
    eng.rate_tracker.apply_rate_limit.return_value = 0
    return eng


def _mock_response(status_code=200, json_data=None):
    resp = Mock()
    resp.status_code = status_code
    resp.json.return_value = json_data or {}
    resp.raise_for_status = Mock()
    if status_code >= 400:
        resp.raise_for_status.side_effect = Exception(f"HTTP {status_code}")
    return resp


# ---------------------------------------------------------------------------
# get_record
# ---------------------------------------------------------------------------


class TestGetRecord:
    @patch(f"{MODULE}.safe_get")
    def test_success(self, mock_get, engine):
        """Returns record dict on success."""
        record = {"id": 12345, "metadata": {"title": "Test Record"}}
        mock_get.return_value = _mock_response(200, record)

        result = engine.get_record(12345)

        assert result["id"] == 12345
        assert result["metadata"]["title"] == "Test Record"

    @patch(f"{MODULE}.safe_get")
    def test_rate_limit_propagates(self, mock_get, engine):
        """RateLimitError is re-raised."""
        mock_get.return_value = _mock_response(429)
        engine._raise_if_rate_limit = Mock(side_effect=RateLimitError("429"))

        with pytest.raises(RateLimitError):
            engine.get_record(12345)

    @patch(f"{MODULE}.safe_get")
    def test_exception_returns_none(self, mock_get, engine):
        """Other exceptions return None."""
        mock_get.side_effect = ConnectionError("fail")

        result = engine.get_record(12345)
        assert result is None


# ---------------------------------------------------------------------------
# search_datasets / search_software
# ---------------------------------------------------------------------------


class TestSpecializedSearch:
    def test_search_datasets_sets_type(self, engine):
        """search_datasets temporarily sets resource_type to 'dataset'."""
        engine.resource_type = None
        with patch.object(
            engine, "run", return_value=[{"title": "Dataset"}]
        ) as mock_run:
            result = engine.search_datasets("climate data")

        assert len(result) == 1
        mock_run.assert_called_once_with("climate data")
        # resource_type should be restored
        assert engine.resource_type is None

    def test_search_software_sets_type(self, engine):
        """search_software temporarily sets resource_type to 'software'."""
        engine.resource_type = "publication"
        with patch.object(engine, "run", return_value=[]) as mock_run:
            engine.search_software("python library")

        mock_run.assert_called_once_with("python library")
        # resource_type should be restored to original
        assert engine.resource_type == "publication"

    def test_search_datasets_restores_type_on_exception(self, engine):
        """resource_type is restored even when run() raises."""
        engine.resource_type = "publication"
        with patch.object(engine, "run", side_effect=Exception("fail")):
            with pytest.raises(Exception, match="fail"):
                engine.search_datasets("broken")

        assert engine.resource_type == "publication"


# ---------------------------------------------------------------------------
# _get_full_content
# ---------------------------------------------------------------------------


class TestGetFullContent:
    def test_full_content_with_files(self, engine):
        """Full content includes file information."""
        items = [
            {
                "title": "Test",
                "authors": ["Alice"],
                "resource_type": "dataset",
                "publication_date": "2024-01-01",
                "doi": "10.5281/zenodo.12345",
                "keywords": ["ML", "AI"],
                "license": "CC-BY-4.0",
                "description": "",
                "_raw": {
                    "metadata": {"description": "Full description here"},
                    "files": [
                        {
                            "key": "data.csv",
                            "size": 1024,
                            "checksum": "md5:abc",  # DevSkim: ignore DS126858 - Zenodo API fixture data, not actual hashing
                        },
                        {
                            "key": "readme.md",
                            "size": 512,
                            "checksum": "md5:def",  # DevSkim: ignore DS126858 - Zenodo API fixture data, not actual hashing
                        },
                    ],
                },
            }
        ]

        results = engine._get_full_content(items)

        assert len(results) == 1
        assert len(results[0]["files"]) == 2
        assert results[0]["files"][0]["filename"] == "data.csv"
        assert results[0]["files"][0]["size"] == 1024
        assert "Full description here" in results[0]["description"]
        assert "_raw" not in results[0]

    def test_full_content_with_references(self, engine):
        """Full content includes references."""
        items = [
            {
                "title": "Test",
                "authors": [],
                "_raw": {
                    "metadata": {
                        "description": "",
                        "keywords": ["test"],
                        "related_identifiers": [{"identifier": "10.1234/ref"}],
                        "references": ["Smith et al., 2020"],
                    },
                    "files": [],
                },
            }
        ]

        results = engine._get_full_content(items)

        assert results[0]["references"] == ["Smith et al., 2020"]
        assert results[0]["related_identifiers"] == [
            {"identifier": "10.1234/ref"}
        ]

    def test_full_content_builds_content_string(self, engine):
        """Content string is built from all available fields."""
        items = [
            {
                "title": "Test",
                "authors": ["Alice", "Bob"],
                "resource_type": "publication",
                "publication_date": "2024-01",
                "doi": "10.5281/zenodo.99",
                "keywords": ["ML"],
                "license": "MIT",
                "description": "",
                "_raw": {
                    "metadata": {
                        "description": "<p>A <b>bold</b> study</p>",
                        "keywords": ["ML"],
                    },
                    "files": [],
                },
            }
        ]

        results = engine._get_full_content(items)
        content = results[0]["content"]

        assert "Authors: Alice, Bob" in content
        assert "Type: publication" in content
        assert "Published: 2024-01" in content
        assert "DOI: 10.5281/zenodo.99" in content
        assert "Keywords: ML" in content
        assert "License: MIT" in content
        assert "A bold study" in content  # HTML stripped

    def test_full_content_without_raw(self, engine):
        """Items without _raw get minimal content."""
        items = [{"title": "No raw", "description": "Basic"}]

        results = engine._get_full_content(items)

        assert len(results) == 1
        assert "_raw" not in results[0]

    def test_full_content_no_files(self, engine):
        """Handles records with no files field."""
        items = [
            {
                "title": "Test",
                "_raw": {
                    "metadata": {"description": ""},
                },
            }
        ]

        results = engine._get_full_content(items)

        assert results[0]["files"] == []

    def test_full_content_html_stripped_from_description(self, engine):
        """HTML tags and entities are cleaned from description."""
        items = [
            {
                "title": "Test",
                "_raw": {
                    "metadata": {
                        "description": "<p>This is &amp; that &lt;important&gt;</p>",
                    },
                    "files": [],
                },
            }
        ]

        results = engine._get_full_content(items)

        assert "&amp;" not in results[0]["description"]
        assert "<p>" not in results[0]["description"]
        assert "This is & that <important>" in results[0]["description"]


# ---------------------------------------------------------------------------
# _get_previews edge cases
# ---------------------------------------------------------------------------


class TestGetPreviewsEdgeCases:
    @patch(f"{MODULE}.safe_get")
    def test_record_parsing_error_continues(self, mock_get, engine):
        """Error parsing one record doesn't stop processing others."""
        mock_get.return_value = _mock_response(
            200,
            {
                "hits": {
                    "total": 2,
                    "hits": [
                        {
                            "id": 1
                        },  # Missing metadata - will still parse (with defaults)
                        {
                            "id": 2,
                            "metadata": {
                                "title": "Good Record",
                                "creators": [{"name": "Author"}],
                            },
                            "links": {},
                        },
                    ],
                }
            },
        )

        previews = engine._get_previews("test")
        # Both should parse since we handle missing fields with defaults
        assert len(previews) == 2

    @patch(f"{MODULE}.safe_get")
    def test_license_info_none(self, mock_get, engine):
        """Handles license_info being None."""
        mock_get.return_value = _mock_response(
            200,
            {
                "hits": {
                    "total": 1,
                    "hits": [
                        {
                            "id": 1,
                            "metadata": {
                                "title": "Test",
                                "creators": [],
                                "license": None,  # Explicitly None
                            },
                            "links": {},
                        }
                    ],
                }
            },
        )

        previews = engine._get_previews("test")
        assert len(previews) == 1
        assert previews[0]["license"] == ""

    @patch(f"{MODULE}.safe_get")
    def test_snippet_with_access_and_license(self, mock_get, engine):
        """Snippet includes access right and license."""
        mock_get.return_value = _mock_response(
            200,
            {
                "hits": {
                    "total": 1,
                    "hits": [
                        {
                            "id": 1,
                            "metadata": {
                                "title": "Test",
                                "creators": [{"name": "Alice"}],
                                "resource_type": {"title": "Dataset"},
                                "access_right": "open",
                                "license": {"id": "cc-by-4.0"},
                                "publication_date": "2024-01-01",
                                "description": "Test desc",
                            },
                            "links": {
                                "self_html": "https://zenodo.org/records/1"
                            },
                        }
                    ],
                }
            },
        )

        previews = engine._get_previews("test")
        snippet = previews[0]["snippet"]

        assert "Alice" in snippet
        assert "Dataset" in snippet
        assert "Open" in snippet
        assert "CC-BY-4.0" in snippet
