"""
Tests for research_service helper functions.

Tests cover:
- _parse_research_metadata: dict/JSON/invalid input handling
- get_citation_formatter: citation mode mapping from settings
- export_report_to_memory: exporter registry integration
"""

from unittest.mock import MagicMock, patch

import pytest

from local_deep_research.web.services.research_service import (
    _parse_research_metadata,
    export_report_to_memory,
    get_citation_formatter,
)


# ---------------------------------------------------------------------------
# _parse_research_metadata
# ---------------------------------------------------------------------------


class TestParseResearchMetadata:
    """Tests for _parse_research_metadata helper."""

    def test_returns_dict_as_is(self):
        """A plain dict input is returned unchanged (as a copy)."""
        original = {"key": "value", "num": 42}
        result = _parse_research_metadata(original)
        assert result == original
        # Should be a copy, not the same object
        assert result is not original

    def test_parses_json_string(self):
        """A valid JSON string is parsed into a dict."""
        json_str = '{"key": "value", "num": 42}'
        result = _parse_research_metadata(json_str)
        assert result == {"key": "value", "num": 42}

    def test_invalid_json_returns_empty_dict(self):
        """An invalid JSON string returns an empty dict."""
        result = _parse_research_metadata("not valid json {{{")
        assert result == {}

    def test_none_returns_empty_dict(self):
        """None input returns an empty dict."""
        result = _parse_research_metadata(None)
        assert result == {}

    def test_integer_returns_empty_dict(self):
        """An integer input returns an empty dict."""
        result = _parse_research_metadata(999)
        assert result == {}

    def test_empty_string_returns_empty_dict(self):
        """An empty string (invalid JSON) returns an empty dict."""
        result = _parse_research_metadata("")
        assert result == {}

    def test_preserves_nested_dict_structure(self):
        """Nested dict structures survive the round-trip."""
        nested = {
            "outer": {"inner": [1, 2, 3]},
            "list": [{"a": 1}, {"b": 2}],
        }
        result = _parse_research_metadata(nested)
        assert result == nested


# ---------------------------------------------------------------------------
# get_citation_formatter
# ---------------------------------------------------------------------------


class TestGetCitationFormatter:
    """Tests for get_citation_formatter with mocked settings."""

    @patch(
        "local_deep_research.config.search_config.get_setting_from_snapshot",
        return_value="number_hyperlinks",
    )
    def test_number_hyperlinks_setting(self, mock_get_setting):
        """'number_hyperlinks' maps to CitationMode.NUMBER_HYPERLINKS."""
        from local_deep_research.text_optimization import CitationMode

        formatter = get_citation_formatter()
        assert formatter.mode == CitationMode.NUMBER_HYPERLINKS

    @patch(
        "local_deep_research.config.search_config.get_setting_from_snapshot",
        return_value="domain_hyperlinks",
    )
    def test_domain_hyperlinks_setting(self, mock_get_setting):
        """'domain_hyperlinks' maps to CitationMode.DOMAIN_HYPERLINKS."""
        from local_deep_research.text_optimization import CitationMode

        formatter = get_citation_formatter()
        assert formatter.mode == CitationMode.DOMAIN_HYPERLINKS

    @patch(
        "local_deep_research.config.search_config.get_setting_from_snapshot",
        return_value="no_hyperlinks",
    )
    def test_no_hyperlinks_setting(self, mock_get_setting):
        """'no_hyperlinks' maps to CitationMode.NO_HYPERLINKS."""
        from local_deep_research.text_optimization import CitationMode

        formatter = get_citation_formatter()
        assert formatter.mode == CitationMode.NO_HYPERLINKS

    @patch(
        "local_deep_research.config.search_config.get_setting_from_snapshot",
        return_value="totally_unknown_format",
    )
    def test_unknown_setting_defaults_to_number_hyperlinks(
        self, mock_get_setting
    ):
        """An unrecognised setting value falls back to NUMBER_HYPERLINKS."""
        from local_deep_research.text_optimization import CitationMode

        formatter = get_citation_formatter()
        assert formatter.mode == CitationMode.NUMBER_HYPERLINKS

    @patch(
        "local_deep_research.config.search_config.get_setting_from_snapshot",
        return_value="number_hyperlinks",
    )
    def test_default_setting_returns_number_hyperlinks(self, mock_get_setting):
        """When the setting returns its default, formatter uses NUMBER_HYPERLINKS."""
        from local_deep_research.text_optimization import CitationMode

        formatter = get_citation_formatter()
        # Verify the setting was requested with the correct default
        mock_get_setting.assert_called_once_with(
            "report.citation_format", "number_hyperlinks"
        )
        assert formatter.mode == CitationMode.NUMBER_HYPERLINKS


# ---------------------------------------------------------------------------
# export_report_to_memory
# ---------------------------------------------------------------------------


class TestExportReportToMemory:
    """Tests for export_report_to_memory with mocked exporter registry."""

    @patch("local_deep_research.exporters.ExportOptions")
    @patch("local_deep_research.exporters.ExporterRegistry")
    def test_calls_get_exporter_with_lowercase_format(
        self, mock_registry, mock_options_cls
    ):
        """The format string is lowered before being passed to get_exporter."""
        mock_exporter = MagicMock()
        mock_result = MagicMock()
        mock_result.content = b"data"
        mock_result.filename = "report.pdf"
        mock_result.mimetype = "application/pdf"
        mock_exporter.export.return_value = mock_result
        mock_registry.get_exporter.return_value = mock_exporter

        export_report_to_memory("# Hello", "PDF", title="My Report")

        mock_registry.get_exporter.assert_called_once_with("pdf")

    @patch("local_deep_research.exporters.ExportOptions")
    @patch("local_deep_research.exporters.ExporterRegistry")
    def test_raises_value_error_for_unsupported_format(
        self, mock_registry, mock_options_cls
    ):
        """When ExporterRegistry.get_exporter returns None, ValueError is raised."""
        mock_registry.get_exporter.return_value = None
        mock_registry.get_available_formats.return_value = ["pdf", "odt"]

        with pytest.raises(ValueError, match="Unsupported export format"):
            export_report_to_memory("# Hello", "bmp")

    @patch("local_deep_research.exporters.ExportOptions")
    @patch("local_deep_research.exporters.ExporterRegistry")
    def test_returns_tuple_of_content_filename_mimetype(
        self, mock_registry, mock_options_cls
    ):
        """Successful export returns (content, filename, mimetype)."""
        mock_exporter = MagicMock()
        mock_result = MagicMock()
        mock_result.content = b"pdf-bytes"
        mock_result.filename = "report.pdf"
        mock_result.mimetype = "application/pdf"
        mock_exporter.export.return_value = mock_result
        mock_registry.get_exporter.return_value = mock_exporter

        content, filename, mimetype = export_report_to_memory(
            "# Report", "pdf", title="Test"
        )

        assert content == b"pdf-bytes"
        assert filename == "report.pdf"
        assert mimetype == "application/pdf"

    @patch("local_deep_research.exporters.ExportOptions")
    @patch("local_deep_research.exporters.ExporterRegistry")
    def test_normalizes_format_to_lowercase(
        self, mock_registry, mock_options_cls
    ):
        """Mixed-case format strings are normalised before lookup."""
        mock_exporter = MagicMock()
        mock_result = MagicMock()
        mock_result.content = b""
        mock_result.filename = "report.odt"
        mock_result.mimetype = "application/vnd.oasis.opendocument.text"
        mock_exporter.export.return_value = mock_result
        mock_registry.get_exporter.return_value = mock_exporter

        export_report_to_memory("# Hello", "OdT")

        mock_registry.get_exporter.assert_called_once_with("odt")
