"""
Extended tests for news/folder_manager.py

Tests cover:
- FolderManager initialization
- get_user_folders() - folder retrieval
- create_folder() - folder creation
- update_folder() - folder updates
- delete_folder() - folder deletion with subscription handling
- get_subscriptions_by_folder() - folder organization
- update_subscription() - subscription updates with interval recalculation
- delete_subscription() - subscription deletion
- get_subscription_stats() - statistics calculation
- _sub_to_dict() - serialization
- Edge cases and error handling
"""

from unittest.mock import MagicMock
from datetime import datetime, timezone, timedelta


class TestFolderManagerInit:
    """Tests for FolderManager initialization."""

    def test_init_stores_session(self):
        """Initialization stores the session."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        manager = FolderManager(mock_session)

        assert manager.session is mock_session

    def test_init_accepts_any_session(self):
        """Accepts any truthy session object."""
        from local_deep_research.news.folder_manager import FolderManager

        # SQLAlchemy session mock
        mock_session = MagicMock()
        mock_session.query = MagicMock()

        manager = FolderManager(mock_session)
        assert manager.session is mock_session


class TestGetUserFolders:
    """Tests for get_user_folders method."""

    def test_returns_all_folders(self):
        """Returns all folders for user."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_folders = [MagicMock(name="A"), MagicMock(name="B")]
        mock_session.query.return_value.order_by.return_value.all.return_value = mock_folders

        manager = FolderManager(mock_session)
        result = manager.get_user_folders("user-123")

        assert result == mock_folders

    def test_returns_empty_list_when_no_folders(self):
        """Returns empty list when no folders exist."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_session.query.return_value.order_by.return_value.all.return_value = []

        manager = FolderManager(mock_session)
        result = manager.get_user_folders("user-123")

        assert result == []

    def test_calls_query(self):
        """Calls query to get folders."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_session.query.return_value.order_by.return_value.all.return_value = []

        manager = FolderManager(mock_session)
        manager.get_user_folders("user-123")

        # Verify query was called
        mock_session.query.assert_called_once()


class TestCreateFolder:
    """Tests for create_folder method."""

    def test_creates_folder_with_name(self):
        """Creates folder with the given name."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        manager = FolderManager(mock_session)

        result = manager.create_folder("My Folder")

        mock_session.add.assert_called_once()
        mock_session.commit.assert_called_once()
        assert result.name == "My Folder"

    def test_creates_folder_with_description(self):
        """Creates folder with name and description."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        manager = FolderManager(mock_session)

        result = manager.create_folder(
            "My Folder", description="Test description"
        )

        assert result.description == "Test description"

    def test_creates_folder_with_generated_uuid(self):
        """Folder gets a generated UUID."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        manager = FolderManager(mock_session)

        result = manager.create_folder("Test")

        # UUID should be 36 characters (UUID format)
        assert len(result.id) == 36


class TestUpdateFolder:
    """Tests for update_folder method."""

    def test_update_existing_folder(self):
        """Updates existing folder."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_folder = MagicMock()
        mock_folder.id = 1
        mock_folder.name = "Old Name"
        mock_folder.updated_at = None
        mock_session.query.return_value.get.return_value = mock_folder

        manager = FolderManager(mock_session)
        result = manager.update_folder(1, name="New Name")

        assert result is mock_folder
        assert mock_folder.name == "New Name"
        assert mock_folder.updated_at is not None
        mock_session.commit.assert_called_once()

    def test_update_nonexistent_folder(self):
        """Returns None for nonexistent folder."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_session.query.return_value.get.return_value = None

        manager = FolderManager(mock_session)
        result = manager.update_folder(999, name="New Name")

        assert result is None

    def test_update_does_not_modify_id(self):
        """Does not modify folder id."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_folder = MagicMock()
        mock_folder.id = 1
        mock_session.query.return_value.get.return_value = mock_folder

        manager = FolderManager(mock_session)
        manager.update_folder(1, id=999)

        assert mock_folder.id == 1

    def test_update_does_not_modify_created_at(self):
        """Does not modify created_at."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        original_created = datetime(2024, 1, 1, tzinfo=timezone.utc)
        mock_folder = MagicMock()
        mock_folder.created_at = original_created
        mock_session.query.return_value.get.return_value = mock_folder

        manager = FolderManager(mock_session)
        new_created = datetime(2024, 6, 1, tzinfo=timezone.utc)
        manager.update_folder(1, created_at=new_created)

        assert mock_folder.created_at == original_created

    def test_update_multiple_fields(self):
        """Updates multiple fields at once."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_folder = MagicMock()
        mock_folder.name = "Old"
        mock_folder.description = "Old desc"
        mock_session.query.return_value.get.return_value = mock_folder

        manager = FolderManager(mock_session)
        manager.update_folder(1, name="New", description="New desc")

        assert mock_folder.name == "New"
        assert mock_folder.description == "New desc"


class TestDeleteFolder:
    """Tests for delete_folder method."""

    def test_delete_existing_folder(self):
        """Deletes existing folder."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_folder = MagicMock()
        mock_folder.name = "Test Folder"
        mock_session.query.return_value.get.return_value = mock_folder

        manager = FolderManager(mock_session)
        result = manager.delete_folder(1)

        assert result is True
        mock_session.delete.assert_called_once_with(mock_folder)
        mock_session.commit.assert_called_once()

    def test_delete_nonexistent_folder(self):
        """Returns False for nonexistent folder."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_session.query.return_value.get.return_value = None

        manager = FolderManager(mock_session)
        result = manager.delete_folder(999)

        assert result is False

    def test_delete_moves_subscriptions_to_target(self):
        """Moves subscriptions to target folder when specified."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_folder = MagicMock()
        mock_folder.name = "Old Folder"
        mock_session.query.return_value.get.return_value = mock_folder

        manager = FolderManager(mock_session)
        manager.delete_folder(1, move_to="New Folder")

        # Should update subscriptions with the old folder name
        mock_session.query.return_value.filter_by.return_value.update.assert_called_once_with(
            {"folder": "New Folder"}
        )

    def test_delete_sets_subscriptions_to_none(self):
        """Sets subscription folder to None when no target."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_folder = MagicMock()
        mock_folder.name = "Old Folder"
        mock_session.query.return_value.get.return_value = mock_folder

        manager = FolderManager(mock_session)
        manager.delete_folder(1, move_to=None)

        mock_session.query.return_value.filter_by.return_value.update.assert_called_once_with(
            {"folder": None}
        )


class TestGetSubscriptionsByFolder:
    """Tests for get_subscriptions_by_folder method."""

    def test_returns_organized_structure(self):
        """Returns proper folder structure."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()

        # Mock folders
        mock_folder = MagicMock()
        mock_folder.name = "Tech"
        mock_folder.to_dict.return_value = {"id": 1, "name": "Tech"}

        # Mock subscriptions
        mock_sub = MagicMock()
        mock_sub.id = "sub-1"
        mock_sub.subscription_type = "search"
        mock_sub.query_or_topic = "AI"
        mock_sub.created_at = datetime.now(timezone.utc)
        mock_sub.last_refresh = None
        mock_sub.next_refresh = None
        mock_sub.refresh_interval_minutes = 60
        mock_sub.status = "active"

        # Setup query chain
        mock_session.query.return_value.order_by.return_value.all.return_value = [
            mock_folder
        ]
        mock_session.query.return_value.filter_by.return_value.all.return_value = [
            mock_sub
        ]

        manager = FolderManager(mock_session)
        result = manager.get_subscriptions_by_folder("user-123")

        assert "folders" in result
        assert "uncategorized" in result

    def test_includes_uncategorized_subscriptions(self):
        """Includes subscriptions without folders."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()

        # No folders
        mock_session.query.return_value.order_by.return_value.all.return_value = []

        # Uncategorized subscription
        mock_sub = MagicMock()
        mock_sub.id = "sub-1"
        mock_sub.subscription_type = "search"
        mock_sub.query_or_topic = "Test"
        mock_sub.created_at = datetime.now(timezone.utc)
        mock_sub.last_refresh = None
        mock_sub.next_refresh = None
        mock_sub.refresh_interval_minutes = 60
        mock_sub.status = "active"

        # Return uncategorized for filter_by(folder=None, status="active")
        mock_session.query.return_value.filter_by.return_value.all.return_value = [
            mock_sub
        ]

        manager = FolderManager(mock_session)
        result = manager.get_subscriptions_by_folder("user-123")

        assert len(result["uncategorized"]) > 0


class TestUpdateSubscriptionExtended:
    """Extended tests for update_subscription method."""

    def test_update_changes_query_or_topic(self):
        """Updates query_or_topic field."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_sub = MagicMock()
        mock_sub.id = "sub-123"
        mock_sub.query_or_topic = "Old Query"
        mock_sub.updated_at = None
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_sub

        manager = FolderManager(mock_session)
        result = manager.update_subscription(
            "sub-123", query_or_topic="New Query"
        )

        assert mock_sub.query_or_topic == "New Query"
        assert result == mock_sub

    def test_update_changes_folder(self):
        """Updates folder field."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_sub = MagicMock()
        mock_sub.id = "sub-123"
        mock_sub.folder = "Old Folder"
        mock_sub.updated_at = None
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_sub

        manager = FolderManager(mock_session)
        manager.update_subscription("sub-123", folder="New Folder")

        assert mock_sub.folder == "New Folder"

    def test_update_refresh_interval_with_existing_last_refresh(self):
        """Recalculates next_refresh based on last_refresh."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_sub = MagicMock()
        mock_sub.id = "sub-123"
        mock_sub.last_refresh = datetime(
            2024, 1, 15, 10, 0, tzinfo=timezone.utc
        )
        mock_sub.next_refresh = datetime(
            2024, 1, 15, 11, 0, tzinfo=timezone.utc
        )
        mock_sub.updated_at = None
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_sub

        manager = FolderManager(mock_session)
        manager.update_subscription("sub-123", refresh_interval_minutes=180)

        # next_refresh = last_refresh + 180 minutes
        expected = datetime(2024, 1, 15, 13, 0, tzinfo=timezone.utc)
        assert mock_sub.next_refresh == expected

    def test_update_refresh_interval_without_last_refresh(self):
        """Calculates next_refresh from now when no last_refresh."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_sub = MagicMock()
        mock_sub.id = "sub-123"
        mock_sub.last_refresh = None
        mock_sub.next_refresh = None
        mock_sub.updated_at = None
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_sub

        manager = FolderManager(mock_session)
        before = datetime.now(timezone.utc)
        manager.update_subscription("sub-123", refresh_interval_minutes=30)
        after = datetime.now(timezone.utc)

        expected_min = before + timedelta(minutes=30)
        expected_max = after + timedelta(minutes=30)
        assert expected_min <= mock_sub.next_refresh <= expected_max

    def test_update_commits_transaction(self):
        """Commits the transaction after update."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_sub = MagicMock()
        mock_sub.updated_at = None
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_sub

        manager = FolderManager(mock_session)
        manager.update_subscription("sub-123", status="paused")

        mock_session.commit.assert_called_once()


class TestDeleteSubscription:
    """Tests for delete_subscription method."""

    def test_delete_existing_subscription(self):
        """Deletes existing subscription."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_sub = MagicMock()
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_sub

        manager = FolderManager(mock_session)
        result = manager.delete_subscription("sub-123")

        assert result is True
        mock_session.delete.assert_called_once_with(mock_sub)
        mock_session.commit.assert_called_once()

    def test_delete_nonexistent_subscription(self):
        """Returns False for nonexistent subscription."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_session.query.return_value.filter_by.return_value.first.return_value = None

        manager = FolderManager(mock_session)
        result = manager.delete_subscription("nonexistent")

        assert result is False
        mock_session.delete.assert_not_called()


class TestGetSubscriptionStats:
    """Tests for get_subscription_stats method."""

    def test_stats_structure(self):
        """Returns correct stats structure."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 10
        mock_session.query.return_value.filter_by.return_value.count.return_value = 5
        mock_session.query.return_value.order_by.return_value.all.return_value = []

        manager = FolderManager(mock_session)
        result = manager.get_subscription_stats("user-123")

        assert "total" in result
        assert "active" in result
        assert "by_type" in result
        assert "folders" in result

    def test_stats_counts_by_type(self):
        """Counts subscriptions by type correctly."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()

        # Setup different counts for different filter combinations
        def mock_filter_by(**kwargs):
            result = MagicMock()
            if (
                kwargs.get("subscription_type") == "search"
                and kwargs.get("status") == "active"
            ):
                result.count.return_value = 3
            elif (
                kwargs.get("subscription_type") == "topic"
                and kwargs.get("status") == "active"
            ):
                result.count.return_value = 2
            elif kwargs.get("status") == "active":
                result.count.return_value = 5
            else:
                result.count.return_value = 0
            return result

        mock_session.query.return_value.count.return_value = 10
        mock_session.query.return_value.filter_by = mock_filter_by
        mock_session.query.return_value.order_by.return_value.all.return_value = []

        manager = FolderManager(mock_session)
        result = manager.get_subscription_stats("user-123")

        assert result["by_type"]["search"] == 3
        assert result["by_type"]["topic"] == 2


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

    def test_includes_all_fields(self):
        """Includes all required fields."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        manager = FolderManager(mock_session)

        mock_sub = MagicMock()
        mock_sub.id = "sub-123"
        mock_sub.subscription_type = "search"
        mock_sub.query_or_topic = "AI news"
        mock_sub.created_at = datetime(2024, 1, 15, 10, 0, tzinfo=timezone.utc)
        mock_sub.last_refresh = datetime(
            2024, 1, 16, 10, 0, tzinfo=timezone.utc
        )
        mock_sub.next_refresh = datetime(
            2024, 1, 16, 11, 0, tzinfo=timezone.utc
        )
        mock_sub.refresh_interval_minutes = 60
        mock_sub.status = "active"

        result = manager._sub_to_dict(mock_sub)

        assert result["id"] == "sub-123"
        assert result["type"] == "search"
        assert result["query_or_topic"] == "AI news"
        assert result["refresh_interval_minutes"] == 60
        assert result["status"] == "active"

    def test_handles_none_timestamps(self):
        """Handles None timestamps gracefully."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        manager = FolderManager(mock_session)

        mock_sub = MagicMock()
        mock_sub.id = "sub-123"
        mock_sub.subscription_type = "topic"
        mock_sub.query_or_topic = "Tech"
        mock_sub.created_at = None
        mock_sub.last_refresh = None
        mock_sub.next_refresh = None
        mock_sub.refresh_interval_minutes = 60
        mock_sub.status = "active"

        result = manager._sub_to_dict(mock_sub)

        assert result["created_at"] is None
        assert result["last_refresh"] is None
        assert result["next_refresh"] is None

    def test_formats_dates_as_iso(self):
        """Formats dates as ISO strings."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        manager = FolderManager(mock_session)

        mock_sub = MagicMock()
        mock_sub.id = "sub-123"
        mock_sub.subscription_type = "search"
        mock_sub.query_or_topic = "Test"
        mock_sub.created_at = datetime(
            2024, 1, 15, 10, 30, 0, tzinfo=timezone.utc
        )
        mock_sub.last_refresh = None
        mock_sub.next_refresh = None
        mock_sub.refresh_interval_minutes = 60
        mock_sub.status = "active"

        result = manager._sub_to_dict(mock_sub)

        assert result["created_at"] == "2024-01-15T10:30:00+00:00"


class TestFolderManagerEdgeCases:
    """Edge cases for FolderManager."""

    def test_empty_folder_name(self):
        """Handles empty folder name."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        manager = FolderManager(mock_session)

        result = manager.create_folder("")
        assert result.name == ""

    def test_unicode_folder_name(self):
        """Handles unicode folder name."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        manager = FolderManager(mock_session)

        result = manager.create_folder("技术新闻 📁")
        assert result.name == "技术新闻 📁"

    def test_very_long_folder_name(self):
        """Handles very long folder name."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        manager = FolderManager(mock_session)

        long_name = "A" * 1000

        result = manager.create_folder(long_name)
        assert result.name == long_name

    def test_update_with_no_changes(self):
        """Handles update with no changes."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_folder = MagicMock()
        mock_folder.name = "Test"
        mock_session.query.return_value.get.return_value = mock_folder

        manager = FolderManager(mock_session)
        result = manager.update_folder(1)  # No kwargs

        assert result is mock_folder
        mock_session.commit.assert_called_once()

    def test_subscription_with_zero_interval(self):
        """Handles subscription with zero refresh interval."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_sub = MagicMock()
        mock_sub.last_refresh = datetime.now(timezone.utc)
        mock_sub.updated_at = None
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_sub

        manager = FolderManager(mock_session)
        manager.update_subscription("sub-123", refresh_interval_minutes=0)

        # next_refresh should be same as last_refresh (0 minutes later)
        assert mock_sub.next_refresh is not None

    def test_stats_with_no_subscriptions(self):
        """Returns zeros when no subscriptions exist."""
        from local_deep_research.news.folder_manager import FolderManager

        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 0
        mock_session.query.return_value.filter_by.return_value.count.return_value = 0
        mock_session.query.return_value.order_by.return_value.all.return_value = []

        manager = FolderManager(mock_session)
        result = manager.get_subscription_stats("user-123")

        assert result["total"] == 0
        assert result["active"] == 0
        assert result["folders"] == 0
