"""Comprehensive tests for DatabaseWithFileBackupStorage."""

from unittest.mock import MagicMock, patch


from local_deep_research.storage.database_with_file_backup import (
    DatabaseWithFileBackupStorage,
)

MODULE = "local_deep_research.storage.database_with_file_backup"


# ---------------------------------------------------------------------------
# __init__
# ---------------------------------------------------------------------------


class TestInit:
    """Tests for __init__."""

    @patch(f"{MODULE}.DatabaseReportStorage")
    def test_without_file_storage_file_storage_is_none(
        self, mock_db_cls, mock_session
    ):
        """file_storage should be None when enable_file_storage is False."""
        storage = DatabaseWithFileBackupStorage(mock_session)

        assert storage.file_storage is None
        assert storage.enable_file_storage is False

    @patch(f"{MODULE}.FileReportStorage")
    @patch(f"{MODULE}.DatabaseReportStorage")
    def test_with_file_storage_creates_file_report_storage(
        self, mock_db_cls, mock_file_cls, mock_session
    ):
        """file_storage should be a FileReportStorage when enabled."""
        storage = DatabaseWithFileBackupStorage(
            mock_session, enable_file_storage=True
        )

        assert storage.enable_file_storage is True
        mock_file_cls.assert_called_once_with()
        assert storage.file_storage is mock_file_cls.return_value

    @patch(f"{MODULE}.DatabaseReportStorage")
    def test_db_storage_created_with_session(self, mock_db_cls, mock_session):
        """db_storage should be created by passing session to DatabaseReportStorage."""
        storage = DatabaseWithFileBackupStorage(mock_session)

        mock_db_cls.assert_called_once_with(mock_session)
        assert storage.db_storage is mock_db_cls.return_value


# ---------------------------------------------------------------------------
# save_report
# ---------------------------------------------------------------------------


class TestSaveReport:
    """Tests for save_report."""

    def test_db_success_no_file_storage_returns_true(
        self, mock_session, sample_report_content
    ):
        """Should return True and only call db_storage when file storage disabled."""
        storage = DatabaseWithFileBackupStorage(mock_session)
        storage.db_storage = MagicMock()
        storage.db_storage.save_report.return_value = True

        result = storage.save_report("r1", sample_report_content)

        assert result is True
        storage.db_storage.save_report.assert_called_once()
        # file_storage is None, nothing else should happen

    def test_db_fails_returns_false_immediately(
        self, mock_session, sample_report_content
    ):
        """Should return False immediately when db save fails; no file attempt."""
        storage = DatabaseWithFileBackupStorage(
            mock_session, enable_file_storage=True
        )
        storage.db_storage = MagicMock()
        storage.db_storage.save_report.return_value = False
        storage.file_storage = MagicMock()

        result = storage.save_report("r1", sample_report_content)

        assert result is False
        storage.file_storage.save_report.assert_not_called()

    def test_db_success_file_success_returns_true(
        self, mock_session, sample_report_content
    ):
        """Should return True when both db and file saves succeed."""
        storage = DatabaseWithFileBackupStorage(
            mock_session, enable_file_storage=True
        )
        storage.db_storage = MagicMock()
        storage.db_storage.save_report.return_value = True
        storage.file_storage = MagicMock()
        storage.file_storage.save_report.return_value = True

        result = storage.save_report("r1", sample_report_content)

        assert result is True
        storage.file_storage.save_report.assert_called_once()

    def test_db_success_file_returns_false_still_returns_true(
        self, mock_session, sample_report_content
    ):
        """Should return True even when file save returns False (db succeeded)."""
        storage = DatabaseWithFileBackupStorage(
            mock_session, enable_file_storage=True
        )
        storage.db_storage = MagicMock()
        storage.db_storage.save_report.return_value = True
        storage.file_storage = MagicMock()
        storage.file_storage.save_report.return_value = False

        result = storage.save_report("r1", sample_report_content)

        assert result is True

    def test_db_success_file_raises_exception_still_returns_true(
        self, mock_session, sample_report_content
    ):
        """Should return True even when file save raises an exception."""
        storage = DatabaseWithFileBackupStorage(
            mock_session, enable_file_storage=True
        )
        storage.db_storage = MagicMock()
        storage.db_storage.save_report.return_value = True
        storage.file_storage = MagicMock()
        storage.file_storage.save_report.side_effect = OSError("disk full")

        result = storage.save_report("r1", sample_report_content)

        assert result is True

    def test_passes_all_arguments_to_db_storage(
        self, mock_session, sample_report_content, sample_metadata
    ):
        """Should forward research_id, content, metadata, username to db_storage."""
        storage = DatabaseWithFileBackupStorage(mock_session)
        storage.db_storage = MagicMock()
        storage.db_storage.save_report.return_value = True

        storage.save_report(
            "r1", sample_report_content, sample_metadata, "alice"
        )

        storage.db_storage.save_report.assert_called_once_with(
            "r1", sample_report_content, sample_metadata, "alice"
        )


# ---------------------------------------------------------------------------
# get_report
# ---------------------------------------------------------------------------


class TestGetReport:
    """Tests for get_report."""

    def test_delegates_to_db_storage(self, mock_session, sample_report_content):
        """Should delegate to db_storage.get_report and return its result."""
        storage = DatabaseWithFileBackupStorage(mock_session)
        storage.db_storage = MagicMock()
        storage.db_storage.get_report.return_value = sample_report_content

        result = storage.get_report("r1", username="bob")

        assert result == sample_report_content
        storage.db_storage.get_report.assert_called_once_with("r1", "bob")

    def test_never_calls_file_storage_even_when_enabled(
        self, mock_session, sample_report_content
    ):
        """Should never read from file storage, even when it is enabled."""
        storage = DatabaseWithFileBackupStorage(
            mock_session, enable_file_storage=True
        )
        storage.db_storage = MagicMock()
        storage.db_storage.get_report.return_value = sample_report_content
        storage.file_storage = MagicMock()

        storage.get_report("r1")

        storage.file_storage.get_report.assert_not_called()


# ---------------------------------------------------------------------------
# delete_report
# ---------------------------------------------------------------------------


class TestDeleteReport:
    """Tests for delete_report."""

    def test_db_success_no_file_storage_returns_true(self, mock_session):
        """Should return True when db delete succeeds and no file storage."""
        storage = DatabaseWithFileBackupStorage(mock_session)
        storage.db_storage = MagicMock()
        storage.db_storage.delete_report.return_value = True

        result = storage.delete_report("r1")

        assert result is True
        storage.db_storage.delete_report.assert_called_once()

    def test_db_success_file_storage_calls_file_delete(self, mock_session):
        """Should call file_storage.delete_report when db delete succeeds."""
        storage = DatabaseWithFileBackupStorage(
            mock_session, enable_file_storage=True
        )
        storage.db_storage = MagicMock()
        storage.db_storage.delete_report.return_value = True
        storage.file_storage = MagicMock()

        storage.delete_report("r1", username="carol")

        storage.file_storage.delete_report.assert_called_once_with(
            "r1", "carol"
        )

    def test_db_fails_returns_false_file_not_called(self, mock_session):
        """Should return False and skip file delete when db delete fails."""
        storage = DatabaseWithFileBackupStorage(
            mock_session, enable_file_storage=True
        )
        storage.db_storage = MagicMock()
        storage.db_storage.delete_report.return_value = False
        storage.file_storage = MagicMock()

        result = storage.delete_report("r1")

        assert result is False
        storage.file_storage.delete_report.assert_not_called()

    def test_db_success_file_raises_exception_still_returns_true(
        self, mock_session
    ):
        """Should return True even when file delete raises an exception."""
        storage = DatabaseWithFileBackupStorage(
            mock_session, enable_file_storage=True
        )
        storage.db_storage = MagicMock()
        storage.db_storage.delete_report.return_value = True
        storage.file_storage = MagicMock()
        storage.file_storage.delete_report.side_effect = OSError(
            "permission denied"
        )

        result = storage.delete_report("r1")

        assert result is True


# ---------------------------------------------------------------------------
# list_reports
# ---------------------------------------------------------------------------


class TestListReports:
    """Tests for list_reports."""

    def test_delegates_to_db_storage(self, mock_session):
        """Should delegate to db_storage.list_reports and return its result."""
        storage = DatabaseWithFileBackupStorage(mock_session)
        storage.db_storage = MagicMock()
        expected = [{"id": "r1"}, {"id": "r2"}]
        storage.db_storage.list_reports.return_value = expected

        result = storage.list_reports(username="dave")

        assert result == expected
        storage.db_storage.list_reports.assert_called_once_with("dave")

    def test_delegates_without_username(self, mock_session):
        """Should delegate with None username when not provided."""
        storage = DatabaseWithFileBackupStorage(mock_session)
        storage.db_storage = MagicMock()
        storage.db_storage.list_reports.return_value = []

        result = storage.list_reports()

        assert result == []
        storage.db_storage.list_reports.assert_called_once_with(None)


# ---------------------------------------------------------------------------
# get_report_with_metadata
# ---------------------------------------------------------------------------


class TestGetReportWithMetadata:
    """Tests for get_report_with_metadata."""

    def test_delegates_to_db_storage(self, mock_session):
        """Should delegate to db_storage.get_report_with_metadata."""
        storage = DatabaseWithFileBackupStorage(mock_session)
        storage.db_storage = MagicMock()
        expected = {"content": "report text", "metadata": {"key": "value"}}
        storage.db_storage.get_report_with_metadata.return_value = expected

        result = storage.get_report_with_metadata("r1", username="eve")

        assert result == expected
        storage.db_storage.get_report_with_metadata.assert_called_once_with(
            "r1", "eve"
        )


# ---------------------------------------------------------------------------
# report_exists
# ---------------------------------------------------------------------------


class TestReportExists:
    """Tests for report_exists."""

    def test_delegates_to_db_storage(self, mock_session):
        """Should delegate to db_storage.report_exists and return its result."""
        storage = DatabaseWithFileBackupStorage(mock_session)
        storage.db_storage = MagicMock()
        storage.db_storage.report_exists.return_value = True

        result = storage.report_exists("r1", username="frank")

        assert result is True
        storage.db_storage.report_exists.assert_called_once_with("r1", "frank")
