"""Tests for IntegratedReportGenerator.__init__ and get_report_generator factory."""

from unittest.mock import MagicMock, patch

import pytest

from local_deep_research.report_generator import (
    IntegratedReportGenerator,
    get_report_generator,
)


MODULE = "local_deep_research.report_generator"


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture()
def mock_get_llm():
    with patch(f"{MODULE}.get_llm") as m:
        m.return_value = MagicMock(name="default_llm")
        yield m


@pytest.fixture()
def mock_advanced_search_system():
    with patch(f"{MODULE}.AdvancedSearchSystem") as m:
        m.return_value = MagicMock(name="constructed_search_system")
        yield m


@pytest.fixture()
def mock_get_setting():
    with patch(f"{MODULE}.get_setting_from_snapshot") as m:
        # By default, return the default value passed to it
        m.side_effect = lambda key, default=None, settings_snapshot=None: (
            default
        )
        yield m


# ---------------------------------------------------------------------------
# get_report_generator factory
# ---------------------------------------------------------------------------


class TestGetReportGenerator:
    """Tests for the get_report_generator factory function."""

    def test_returns_integrated_report_generator_instance(
        self, mock_get_llm, mock_advanced_search_system, mock_get_setting
    ):
        result = get_report_generator()
        assert isinstance(result, IntegratedReportGenerator)

    def test_passes_search_system_to_constructor(self, mock_get_setting):
        search_system = MagicMock(name="my_search_system")
        search_system.model = MagicMock(name="ss_model")

        result = get_report_generator(search_system=search_system)

        assert result.search_system is search_system


# ---------------------------------------------------------------------------
# __init__ with search_system provided
# ---------------------------------------------------------------------------


class TestInitWithSearchSystem:
    """Tests for __init__ when search_system is provided."""

    def test_uses_provided_search_system(self, mock_get_setting):
        search_system = MagicMock(name="my_search_system")
        search_system.model = MagicMock(name="ss_model")

        gen = IntegratedReportGenerator(search_system=search_system)

        assert gen.search_system is search_system

    def test_uses_search_system_model_when_no_llm(self, mock_get_setting):
        search_system = MagicMock(name="my_search_system")
        ss_model = MagicMock(name="ss_model")
        search_system.model = ss_model

        gen = IntegratedReportGenerator(search_system=search_system)

        assert gen.model is ss_model

    def test_uses_provided_llm_over_search_system_model(self, mock_get_setting):
        search_system = MagicMock(name="my_search_system")
        search_system.model = MagicMock(name="ss_model")
        custom_llm = MagicMock(name="custom_llm")

        gen = IntegratedReportGenerator(
            search_system=search_system, llm=custom_llm
        )

        assert gen.model is custom_llm


# ---------------------------------------------------------------------------
# __init__ with llm only (no search_system)
# ---------------------------------------------------------------------------


class TestInitWithLlmOnly:
    """Tests for __init__ when only llm is provided."""

    def test_creates_advanced_search_system_with_llm(
        self, mock_advanced_search_system, mock_get_setting
    ):
        custom_llm = MagicMock(name="custom_llm")

        IntegratedReportGenerator(llm=custom_llm)

        mock_advanced_search_system.assert_called_once_with(llm=custom_llm)

    def test_sets_model_to_provided_llm(
        self, mock_advanced_search_system, mock_get_setting
    ):
        custom_llm = MagicMock(name="custom_llm")

        gen = IntegratedReportGenerator(llm=custom_llm)

        assert gen.model is custom_llm


# ---------------------------------------------------------------------------
# __init__ with neither search_system nor llm
# ---------------------------------------------------------------------------


class TestInitWithNeither:
    """Tests for __init__ when neither search_system nor llm is provided."""

    def test_calls_get_llm(
        self, mock_get_llm, mock_advanced_search_system, mock_get_setting
    ):
        IntegratedReportGenerator()

        mock_get_llm.assert_called_once()

    def test_creates_advanced_search_system_with_default_model(
        self, mock_get_llm, mock_advanced_search_system, mock_get_setting
    ):
        IntegratedReportGenerator()

        mock_advanced_search_system.assert_called_once_with(
            llm=mock_get_llm.return_value
        )


# ---------------------------------------------------------------------------
# Settings / attributes
# ---------------------------------------------------------------------------


class TestSettings:
    """Tests for configurable attributes set during __init__."""

    def test_searches_per_section_defaults_to_2(
        self, mock_get_llm, mock_advanced_search_system, mock_get_setting
    ):
        gen = IntegratedReportGenerator()

        assert gen.searches_per_section == 2

    def test_custom_searches_per_section_is_stored(
        self, mock_get_llm, mock_advanced_search_system, mock_get_setting
    ):
        gen = IntegratedReportGenerator(searches_per_section=5)

        assert gen.searches_per_section == 5

    def test_max_context_sections_reads_from_snapshot(
        self, mock_get_llm, mock_advanced_search_system, mock_get_setting
    ):
        snapshot = {"report.max_context_sections": 10}
        mock_get_setting.side_effect = None
        mock_get_setting.return_value = 10

        gen = IntegratedReportGenerator(settings_snapshot=snapshot)

        # Verify get_setting_from_snapshot was called with correct key
        calls = mock_get_setting.call_args_list
        section_call = [
            c for c in calls if c[0][0] == "report.max_context_sections"
        ]
        assert len(section_call) == 1
        assert section_call[0].kwargs["settings_snapshot"] is snapshot
        assert gen.max_context_sections == 10

    def test_max_context_chars_reads_from_snapshot(
        self, mock_get_llm, mock_advanced_search_system, mock_get_setting
    ):
        snapshot = {"report.max_context_chars": 8000}
        mock_get_setting.side_effect = None
        mock_get_setting.return_value = 8000

        gen = IntegratedReportGenerator(settings_snapshot=snapshot)

        calls = mock_get_setting.call_args_list
        chars_call = [c for c in calls if c[0][0] == "report.max_context_chars"]
        assert len(chars_call) == 1
        assert chars_call[0].kwargs["settings_snapshot"] is snapshot
        assert gen.max_context_chars == 8000

    def test_defaults_for_max_context_sections_and_chars(
        self, mock_get_llm, mock_advanced_search_system, mock_get_setting
    ):
        gen = IntegratedReportGenerator()

        # mock_get_setting side_effect returns the default kwarg,
        # so we expect the source-code defaults: 3 and 4000
        assert gen.max_context_sections == 3
        assert gen.max_context_chars == 4000
