"""
Deep behavioral tests for folder_manager.py pure logic.
Tests folder update validation, subscription organization,
next_refresh recalculation, and stats aggregation.
"""

from datetime import datetime, timedelta, timezone


# --- Update folder field validation ---


class TestUpdateFolderFieldValidation:
    """Tests for folder update field validation."""

    PROTECTED_FIELDS = {"id", "created_at"}

    def _is_updateable(self, field):
        return field not in self.PROTECTED_FIELDS

    def test_name_updateable(self):
        assert self._is_updateable("name") is True

    def test_description_updateable(self):
        assert self._is_updateable("description") is True

    def test_id_not_updateable(self):
        assert self._is_updateable("id") is False

    def test_created_at_not_updateable(self):
        assert self._is_updateable("created_at") is False

    def test_updated_at_updateable(self):
        assert self._is_updateable("updated_at") is True


class TestFilterUpdateKwargs:
    """Tests for filtering kwargs to valid fields."""

    PROTECTED = {"id", "created_at"}

    def _filter_kwargs(self, kwargs):
        return {k: v for k, v in kwargs.items() if k not in self.PROTECTED}

    def test_filters_id(self):
        result = self._filter_kwargs({"name": "New", "id": "should-not-update"})
        assert "id" not in result
        assert result["name"] == "New"

    def test_filters_created_at(self):
        result = self._filter_kwargs({"created_at": "2025-01-01"})
        assert "created_at" not in result

    def test_preserves_valid_fields(self):
        result = self._filter_kwargs({"name": "X", "description": "Y"})
        assert result == {"name": "X", "description": "Y"}

    def test_empty_after_filter(self):
        result = self._filter_kwargs({"id": "1", "created_at": "X"})
        assert result == {}


# --- Subscription update field validation ---


class TestSubscriptionUpdateFieldValidation:
    """Tests for subscription update field validation."""

    PROTECTED = {"id", "created_at"}

    def _is_updateable(self, field):
        return field not in self.PROTECTED

    def test_folder_updateable(self):
        assert self._is_updateable("folder") is True

    def test_refresh_interval_updateable(self):
        assert self._is_updateable("refresh_interval_minutes") is True

    def test_status_updateable(self):
        assert self._is_updateable("status") is True

    def test_id_not_updateable(self):
        assert self._is_updateable("id") is False

    def test_created_at_not_updateable(self):
        assert self._is_updateable("created_at") is False


# --- Next refresh recalculation ---


class TestNextRefreshRecalculation:
    """Tests for next_refresh recalculation on interval change."""

    def _should_recalc(self, kwargs):
        return "refresh_interval_minutes" in kwargs

    def test_interval_change_needs_recalc(self):
        assert self._should_recalc({"refresh_interval_minutes": 120}) is True

    def test_name_change_no_recalc(self):
        assert self._should_recalc({"name": "New Name"}) is False

    def test_folder_change_no_recalc(self):
        assert self._should_recalc({"folder": "News"}) is False


class TestNextRefreshFromLastRefresh:
    """Tests for calculating next_refresh from last_refresh."""

    def _calc_next(self, last_refresh, interval_minutes):
        if last_refresh:
            return last_refresh + timedelta(minutes=interval_minutes)
        return datetime.now(timezone.utc) + timedelta(minutes=interval_minutes)

    def test_with_last_refresh(self):
        last = datetime(2025, 6, 15, 10, 0, tzinfo=timezone.utc)
        result = self._calc_next(last, 60)
        assert result == datetime(2025, 6, 15, 11, 0, tzinfo=timezone.utc)

    def test_without_last_refresh(self):
        before = datetime.now(timezone.utc)
        result = self._calc_next(None, 60)
        after = datetime.now(timezone.utc)
        expected_min = before + timedelta(minutes=60)
        expected_max = after + timedelta(minutes=60)
        assert expected_min <= result <= expected_max


# --- Delete folder subscription handling ---


class TestDeleteFolderSubscriptionHandling:
    """Tests for handling subscriptions when deleting folder."""

    def _get_move_target(self, move_to):
        if move_to:
            return move_to
        return None

    def test_move_to_target(self):
        assert self._get_move_target("Archive") == "Archive"

    def test_no_target_is_none(self):
        assert self._get_move_target(None) is None

    def test_empty_string_target(self):
        # Empty string is falsy
        assert self._get_move_target("") is None


# --- Subscriptions by folder organization ---


class TestSubscriptionsByFolderOrganization:
    """Tests for organizing subscriptions by folder."""

    def _build_result_structure(self):
        return {"folders": [], "uncategorized": []}

    def test_initial_structure(self):
        result = self._build_result_structure()
        assert result["folders"] == []
        assert result["uncategorized"] == []

    def test_folders_is_list(self):
        result = self._build_result_structure()
        assert isinstance(result["folders"], list)

    def test_uncategorized_is_list(self):
        result = self._build_result_structure()
        assert isinstance(result["uncategorized"], list)


class TestFolderEntryStructure:
    """Tests for folder entry structure in result."""

    def _build_folder_entry(self, folder_dict, subscriptions):
        return {
            "folder": folder_dict,
            "subscriptions": subscriptions,
        }

    def test_has_folder(self):
        result = self._build_folder_entry({"id": "f1", "name": "News"}, [])
        assert result["folder"]["name"] == "News"

    def test_has_subscriptions(self):
        subs = [{"id": "s1"}, {"id": "s2"}]
        result = self._build_folder_entry({}, subs)
        assert len(result["subscriptions"]) == 2


# --- Subscription dict mapping ---


class TestSubscriptionDictMapping:
    """Tests for _sub_to_dict mapping."""

    def _sub_to_dict(self, sub_dict):
        return {
            "id": sub_dict.get("id"),
            "type": sub_dict.get("subscription_type"),
            "query_or_topic": sub_dict.get("query_or_topic"),
            "created_at": sub_dict.get("created_at"),
            "last_refresh": sub_dict.get("last_refresh"),
            "next_refresh": sub_dict.get("next_refresh"),
            "refresh_interval_minutes": sub_dict.get(
                "refresh_interval_minutes"
            ),
            "status": sub_dict.get("status"),
        }

    def test_maps_id(self):
        result = self._sub_to_dict({"id": "s123"})
        assert result["id"] == "s123"

    def test_maps_type(self):
        result = self._sub_to_dict({"subscription_type": "search"})
        assert result["type"] == "search"

    def test_maps_query(self):
        result = self._sub_to_dict({"query_or_topic": "AI news"})
        assert result["query_or_topic"] == "AI news"

    def test_maps_interval(self):
        result = self._sub_to_dict({"refresh_interval_minutes": 60})
        assert result["refresh_interval_minutes"] == 60

    def test_maps_status(self):
        result = self._sub_to_dict({"status": "active"})
        assert result["status"] == "active"

    def test_missing_fields_none(self):
        result = self._sub_to_dict({})
        assert result["id"] is None
        assert result["type"] is None


# --- Isoformat datetime handling ---


class TestIsoformatDatetimeHandling:
    """Tests for datetime isoformat conversion patterns."""

    def _to_iso_or_none(self, dt):
        return dt.isoformat() if dt else None

    def test_datetime_to_iso(self):
        dt = datetime(2025, 6, 15, 10, 30, 0, tzinfo=timezone.utc)
        result = self._to_iso_or_none(dt)
        assert "2025-06-15" in result
        assert "10:30:00" in result

    def test_none_datetime(self):
        assert self._to_iso_or_none(None) is None


# --- Subscription stats aggregation ---


class TestSubscriptionStatsAggregation:
    """Tests for subscription statistics calculation."""

    def _calc_stats(self, total, active, by_type, folder_count):
        return {
            "total": total,
            "active": active,
            "by_type": by_type,
            "folders": folder_count,
        }

    def test_basic_stats(self):
        result = self._calc_stats(10, 8, {"search": 5, "topic": 3}, 2)
        assert result["total"] == 10
        assert result["active"] == 8
        assert result["by_type"]["search"] == 5
        assert result["folders"] == 2

    def test_no_subscriptions(self):
        result = self._calc_stats(0, 0, {"search": 0, "topic": 0}, 0)
        assert result["total"] == 0
        assert result["active"] == 0

    def test_all_active(self):
        result = self._calc_stats(5, 5, {}, 1)
        assert result["total"] == result["active"]


# --- Type counting ---


class TestTypeCountingPatterns:
    """Tests for subscription type counting."""

    def _count_by_type(self, subscriptions):
        counts = {"search": 0, "topic": 0}
        for sub in subscriptions:
            sub_type = sub.get("subscription_type")
            if sub_type in counts:
                counts[sub_type] += 1
        return counts

    def test_count_search(self):
        subs = [
            {"subscription_type": "search"},
            {"subscription_type": "search"},
        ]
        result = self._count_by_type(subs)
        assert result["search"] == 2

    def test_count_topic(self):
        subs = [{"subscription_type": "topic"}]
        result = self._count_by_type(subs)
        assert result["topic"] == 1

    def test_count_mixed(self):
        subs = [
            {"subscription_type": "search"},
            {"subscription_type": "topic"},
            {"subscription_type": "search"},
        ]
        result = self._count_by_type(subs)
        assert result["search"] == 2
        assert result["topic"] == 1

    def test_count_empty(self):
        result = self._count_by_type([])
        assert result["search"] == 0
        assert result["topic"] == 0

    def test_unknown_type_ignored(self):
        subs = [{"subscription_type": "unknown"}]
        result = self._count_by_type(subs)
        assert result["search"] == 0
        assert result["topic"] == 0


# --- Active status filter ---


class TestActiveStatusFilter:
    """Tests for filtering active subscriptions."""

    def _is_active(self, sub):
        return sub.get("status") == "active"

    def test_active_status(self):
        assert self._is_active({"status": "active"}) is True

    def test_paused_status(self):
        assert self._is_active({"status": "paused"}) is False

    def test_expired_status(self):
        assert self._is_active({"status": "expired"}) is False

    def test_no_status(self):
        assert self._is_active({}) is False


# --- Folder filter by name ---


class TestFolderFilterByName:
    """Tests for filtering subscriptions by folder name."""

    def _matches_folder(self, sub, folder_name):
        return sub.get("folder") == folder_name

    def test_matching_folder(self):
        sub = {"folder": "News"}
        assert self._matches_folder(sub, "News") is True

    def test_different_folder(self):
        sub = {"folder": "Tech"}
        assert self._matches_folder(sub, "News") is False

    def test_no_folder(self):
        sub = {}
        assert self._matches_folder(sub, "News") is False

    def test_none_folder(self):
        sub = {"folder": None}
        assert self._matches_folder(sub, None) is True


# --- Uncategorized filter ---


class TestUncategorizedFilter:
    """Tests for filtering uncategorized subscriptions."""

    def _is_uncategorized(self, sub):
        return sub.get("folder") is None

    def test_no_folder(self):
        assert self._is_uncategorized({}) is True

    def test_none_folder(self):
        assert self._is_uncategorized({"folder": None}) is True

    def test_has_folder(self):
        assert self._is_uncategorized({"folder": "News"}) is False

    def test_empty_string_folder(self):
        # Empty string is not None
        assert self._is_uncategorized({"folder": ""}) is False


# --- Folder to dict ---


class TestFolderToDict:
    """Tests for folder dict structure."""

    def _folder_dict_fields(self):
        return ["id", "name", "description", "created_at", "updated_at"]

    def test_has_id(self):
        assert "id" in self._folder_dict_fields()

    def test_has_name(self):
        assert "name" in self._folder_dict_fields()

    def test_has_description(self):
        assert "description" in self._folder_dict_fields()

    def test_has_created_at(self):
        assert "created_at" in self._folder_dict_fields()

    def test_has_updated_at(self):
        assert "updated_at" in self._folder_dict_fields()
