"""
Deep behavioral tests for FolderManager.
Tests CRUD operations, subscription management, and serialization with mocked sessions.
"""

from datetime import datetime, timezone
from unittest.mock import Mock, MagicMock, PropertyMock

from local_deep_research.news.folder_manager import FolderManager


def _make_mock_session():
    """Create a mock SQLAlchemy session."""
    session = MagicMock()
    return session


def _make_mock_sub(
    sub_id="sub-1",
    sub_type="search",
    query="AI news",
    status="active",
    folder=None,
    created_at=None,
    last_refresh=None,
    next_refresh=None,
    refresh_interval=240,
):
    """Create a mock subscription model object."""
    sub = Mock()
    sub.id = sub_id
    sub.subscription_type = sub_type
    sub.query_or_topic = query
    sub.status = status
    sub.folder = folder
    sub.created_at = created_at or datetime(2025, 1, 1, tzinfo=timezone.utc)
    sub.last_refresh = last_refresh
    sub.next_refresh = next_refresh
    sub.refresh_interval_minutes = refresh_interval
    sub.updated_at = None
    return sub


def _make_mock_folder(
    folder_id="f-1",
    name="Tech News",
    description="Technology news",
):
    """Create a mock folder model object."""
    folder = Mock()
    folder.id = folder_id
    folder.name = name
    folder.description = description
    folder.created_at = datetime(2025, 1, 1, tzinfo=timezone.utc)
    folder.updated_at = None
    folder.to_dict.return_value = {
        "id": folder_id,
        "name": name,
        "description": description,
    }
    return folder


# --- _sub_to_dict ---


class TestSubToDict:
    """Tests for the _sub_to_dict helper method."""

    def test_includes_id(self):
        fm = FolderManager(_make_mock_session())
        sub = _make_mock_sub(sub_id="test-123")
        result = fm._sub_to_dict(sub)
        assert result["id"] == "test-123"

    def test_includes_type(self):
        fm = FolderManager(_make_mock_session())
        sub = _make_mock_sub(sub_type="topic")
        result = fm._sub_to_dict(sub)
        assert result["type"] == "topic"

    def test_includes_query_or_topic(self):
        fm = FolderManager(_make_mock_session())
        sub = _make_mock_sub(query="climate change")
        result = fm._sub_to_dict(sub)
        assert result["query_or_topic"] == "climate change"

    def test_created_at_isoformat(self):
        fm = FolderManager(_make_mock_session())
        dt = datetime(2025, 6, 15, 12, 0, 0, tzinfo=timezone.utc)
        sub = _make_mock_sub(created_at=dt)
        result = fm._sub_to_dict(sub)
        assert result["created_at"] == dt.isoformat()

    def test_created_at_none(self):
        fm = FolderManager(_make_mock_session())
        sub = _make_mock_sub()
        sub.created_at = None
        result = fm._sub_to_dict(sub)
        assert result["created_at"] is None

    def test_last_refresh_none(self):
        fm = FolderManager(_make_mock_session())
        sub = _make_mock_sub(last_refresh=None)
        result = fm._sub_to_dict(sub)
        assert result["last_refresh"] is None

    def test_last_refresh_isoformat(self):
        fm = FolderManager(_make_mock_session())
        dt = datetime(2025, 6, 15, 14, 30, 0, tzinfo=timezone.utc)
        sub = _make_mock_sub(last_refresh=dt)
        result = fm._sub_to_dict(sub)
        assert result["last_refresh"] == dt.isoformat()

    def test_next_refresh_none(self):
        fm = FolderManager(_make_mock_session())
        sub = _make_mock_sub(next_refresh=None)
        result = fm._sub_to_dict(sub)
        assert result["next_refresh"] is None

    def test_next_refresh_isoformat(self):
        fm = FolderManager(_make_mock_session())
        dt = datetime(2025, 6, 16, 0, 0, 0, tzinfo=timezone.utc)
        sub = _make_mock_sub(next_refresh=dt)
        result = fm._sub_to_dict(sub)
        assert result["next_refresh"] == dt.isoformat()

    def test_includes_refresh_interval(self):
        fm = FolderManager(_make_mock_session())
        sub = _make_mock_sub(refresh_interval=120)
        result = fm._sub_to_dict(sub)
        assert result["refresh_interval_minutes"] == 120

    def test_includes_status(self):
        fm = FolderManager(_make_mock_session())
        sub = _make_mock_sub(status="paused")
        result = fm._sub_to_dict(sub)
        assert result["status"] == "paused"

    def test_all_keys_present(self):
        fm = FolderManager(_make_mock_session())
        sub = _make_mock_sub()
        result = fm._sub_to_dict(sub)
        expected_keys = {
            "id",
            "type",
            "query_or_topic",
            "created_at",
            "last_refresh",
            "next_refresh",
            "refresh_interval_minutes",
            "status",
        }
        assert set(result.keys()) == expected_keys


# --- create_folder ---


class TestCreateFolder:
    """Tests for folder creation."""

    def test_creates_folder_with_name(self):
        session = _make_mock_session()
        fm = FolderManager(session)
        fm.create_folder("My Folder")
        session.add.assert_called_once()
        session.commit.assert_called_once()

    def test_creates_folder_with_description(self):
        session = _make_mock_session()
        fm = FolderManager(session)
        fm.create_folder("My Folder", description="A test folder")
        added_folder = session.add.call_args[0][0]
        assert added_folder.description == "A test folder"

    def test_folder_has_uuid_id(self):
        import uuid

        session = _make_mock_session()
        fm = FolderManager(session)
        fm.create_folder("Test")
        added_folder = session.add.call_args[0][0]
        uuid.UUID(added_folder.id)  # Should not raise


# --- update_folder ---


class TestUpdateFolder:
    """Tests for folder updates."""

    def test_returns_none_for_nonexistent(self):
        session = _make_mock_session()
        session.query.return_value.get.return_value = None
        fm = FolderManager(session)
        result = fm.update_folder("nonexistent", name="New Name")
        assert result is None

    def test_updates_name(self):
        session = _make_mock_session()
        folder = _make_mock_folder()
        folder.name = "Old Name"
        type(folder).id = PropertyMock(return_value="f-1")
        type(folder).created_at = PropertyMock(
            return_value=datetime(2025, 1, 1, tzinfo=timezone.utc)
        )
        session.query.return_value.get.return_value = folder
        fm = FolderManager(session)
        result = fm.update_folder("f-1", name="New Name")
        assert result is not None
        session.commit.assert_called_once()

    def test_does_not_update_id(self):
        session = _make_mock_session()
        folder = _make_mock_folder()
        session.query.return_value.get.return_value = folder
        fm = FolderManager(session)
        fm.update_folder("f-1", id="should-not-change")
        # id is in the protected list, so setattr should not be called for it

    def test_does_not_update_created_at(self):
        session = _make_mock_session()
        folder = _make_mock_folder()
        session.query.return_value.get.return_value = folder
        fm = FolderManager(session)
        fm.update_folder("f-1", created_at="should-not-change")
        # created_at is in the protected list

    def test_sets_updated_at(self):
        session = _make_mock_session()
        folder = _make_mock_folder()
        session.query.return_value.get.return_value = folder
        fm = FolderManager(session)
        fm.update_folder("f-1", name="Updated")
        assert folder.updated_at is not None


# --- delete_folder ---


class TestDeleteFolder:
    """Tests for folder deletion."""

    def test_returns_false_for_nonexistent(self):
        session = _make_mock_session()
        session.query.return_value.get.return_value = None
        fm = FolderManager(session)
        assert fm.delete_folder("nonexistent") is False

    def test_deletes_existing_folder(self):
        session = _make_mock_session()
        folder = _make_mock_folder()
        session.query.return_value.get.return_value = folder
        fm = FolderManager(session)
        result = fm.delete_folder("f-1")
        assert result is True
        session.delete.assert_called_once_with(folder)
        session.commit.assert_called_once()

    def test_moves_subscriptions_when_move_to(self):
        session = _make_mock_session()
        folder = _make_mock_folder(name="Old Folder")
        session.query.return_value.get.return_value = folder
        fm = FolderManager(session)
        fm.delete_folder("f-1", move_to="New Folder")
        # Should update subscriptions' folder
        session.query.return_value.filter_by.return_value.update.assert_called()

    def test_sets_folder_none_when_no_move_to(self):
        session = _make_mock_session()
        folder = _make_mock_folder()
        session.query.return_value.get.return_value = folder
        fm = FolderManager(session)
        fm.delete_folder("f-1")
        # Should set folder to None for orphaned subscriptions
        session.query.return_value.filter_by.return_value.update.assert_called()


# --- delete_subscription ---


class TestDeleteSubscription:
    """Tests for subscription deletion."""

    def test_returns_false_for_nonexistent(self):
        session = _make_mock_session()
        session.query.return_value.filter_by.return_value.first.return_value = (
            None
        )
        fm = FolderManager(session)
        assert fm.delete_subscription("nonexistent") is False

    def test_deletes_existing_subscription(self):
        session = _make_mock_session()
        sub = _make_mock_sub()
        session.query.return_value.filter_by.return_value.first.return_value = (
            sub
        )
        fm = FolderManager(session)
        result = fm.delete_subscription("sub-1")
        assert result is True
        session.delete.assert_called_once_with(sub)
        session.commit.assert_called_once()


# --- update_subscription ---


class TestUpdateSubscription:
    """Tests for subscription updates."""

    def test_returns_none_for_nonexistent(self):
        session = _make_mock_session()
        session.query.return_value.filter_by.return_value.first.return_value = (
            None
        )
        fm = FolderManager(session)
        result = fm.update_subscription("nonexistent", status="paused")
        assert result is None

    def test_updates_status(self):
        session = _make_mock_session()
        sub = _make_mock_sub()
        session.query.return_value.filter_by.return_value.first.return_value = (
            sub
        )
        fm = FolderManager(session)
        result = fm.update_subscription("sub-1", status="paused")
        assert result is not None
        session.commit.assert_called_once()

    def test_does_not_update_id(self):
        session = _make_mock_session()
        sub = _make_mock_sub()
        session.query.return_value.filter_by.return_value.first.return_value = (
            sub
        )
        fm = FolderManager(session)
        fm.update_subscription("sub-1", id="should-not-change")
        # id is protected

    def test_recalculates_next_refresh_on_interval_change(self):
        session = _make_mock_session()
        sub = _make_mock_sub()
        sub.last_refresh = datetime(2025, 6, 15, 12, 0, 0, tzinfo=timezone.utc)
        sub.next_refresh = datetime(2025, 6, 15, 16, 0, 0, tzinfo=timezone.utc)
        session.query.return_value.filter_by.return_value.first.return_value = (
            sub
        )
        fm = FolderManager(session)
        fm.update_subscription("sub-1", refresh_interval_minutes=120)
        # next_refresh should be last_refresh + 120 minutes
        expected = datetime(2025, 6, 15, 14, 0, 0, tzinfo=timezone.utc)
        assert sub.next_refresh == expected

    def test_uses_now_when_no_last_refresh(self):
        session = _make_mock_session()
        sub = _make_mock_sub()
        sub.last_refresh = None
        sub.next_refresh = None
        session.query.return_value.filter_by.return_value.first.return_value = (
            sub
        )
        fm = FolderManager(session)
        fm.update_subscription("sub-1", refresh_interval_minutes=60)
        # Should set next_refresh to now + 60 minutes
        assert sub.next_refresh is not None

    def test_sets_updated_at(self):
        session = _make_mock_session()
        sub = _make_mock_sub()
        session.query.return_value.filter_by.return_value.first.return_value = (
            sub
        )
        fm = FolderManager(session)
        fm.update_subscription("sub-1", status="active")
        assert sub.updated_at is not None


# --- get_subscription_stats ---


class TestGetSubscriptionStats:
    """Tests for subscription statistics."""

    def test_returns_dict_with_expected_keys(self):
        session = _make_mock_session()
        session.query.return_value.count.return_value = 0
        session.query.return_value.filter_by.return_value.count.return_value = 0
        session.query.return_value.order_by.return_value.all.return_value = []
        fm = FolderManager(session)
        result = fm.get_subscription_stats("user1")
        assert "total" in result
        assert "active" in result
        assert "by_type" in result
        assert "folders" in result

    def test_by_type_includes_search_and_topic(self):
        session = _make_mock_session()
        session.query.return_value.count.return_value = 5
        session.query.return_value.filter_by.return_value.count.return_value = 2
        session.query.return_value.order_by.return_value.all.return_value = []
        fm = FolderManager(session)
        result = fm.get_subscription_stats("user1")
        assert "search" in result["by_type"]
        assert "topic" in result["by_type"]
