"""Task #156: MessagingService.propagate_sessions_to_subtask.

Tests the helper's call shape and idempotency contract without going
through the DB. Integration coverage (real DB, real linking) lives in
``tests/integration/test_messaging_service.py``.
"""

from __future__ import annotations

from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch
from uuid import uuid4

import pytest
from roboco.models.session import SessionTaskRelationshipType
from roboco.services.messaging import MessagingService


def _link(session_id: object, relationship_type: str) -> MagicMock:
    link = MagicMock()
    link.session_id = session_id
    link.relationship_type = relationship_type
    return link


@pytest.mark.asyncio
async def test_propagate_links_every_parent_session_to_subtask() -> None:
    """Every link on the parent gets re-attached to the new subtask."""
    svc = MessagingService.__new__(MessagingService)
    parent_session = uuid4()
    review_session = uuid4()
    svc.get_sessions_for_task = AsyncMock(  # type: ignore[method-assign]
        return_value=[
            _link(parent_session, "discussion"),
            _link(review_session, "review"),
        ]
    )

    calls: list[dict[str, Any]] = []

    async def fake_link(**kwargs: Any) -> Any:
        calls.append(kwargs)
        link = MagicMock()
        link.session_id = kwargs["session_id"]
        link.task_id = kwargs["task_id"]
        return link

    parent_id = uuid4()
    subtask_id = uuid4()
    added_by = uuid4()
    with patch.object(svc, "link_session_to_task", new=fake_link):
        out = await svc.propagate_sessions_to_subtask(parent_id, subtask_id, added_by)
    expected_sessions = {parent_session, review_session}
    assert len(out) == len(expected_sessions)
    assert {c["session_id"] for c in calls} == expected_sessions
    # Every propagated link must be non-primary — primary is the subtask's
    # own slot, never inherited from the parent.
    assert all(c["is_primary"] is False for c in calls)
    # Every call must target the new subtask (not the parent).
    assert {c["task_id"] for c in calls} == {subtask_id}
    # Relationship types preserved.
    rels = {c["relationship_type"] for c in calls}
    assert SessionTaskRelationshipType.DISCUSSION in rels
    assert SessionTaskRelationshipType.REVIEW in rels


@pytest.mark.asyncio
async def test_propagate_no_parent_sessions_returns_empty() -> None:
    """When the parent has no session links, propagation is a no-op."""
    svc = MessagingService.__new__(MessagingService)
    svc.get_sessions_for_task = AsyncMock(return_value=[])  # type: ignore[method-assign]
    svc.link_session_to_task = AsyncMock()  # type: ignore[method-assign]

    out = await svc.propagate_sessions_to_subtask(uuid4(), uuid4(), uuid4())
    assert out == []
    svc.link_session_to_task.assert_not_awaited()


@pytest.mark.asyncio
async def test_propagate_unknown_relationship_type_defaults_to_discussion() -> None:
    """Garbage relationship_type on the parent link doesn't crash; it
    defaults to DISCUSSION so the subtask is still linked."""
    svc = MessagingService.__new__(MessagingService)
    svc.get_sessions_for_task = AsyncMock(  # type: ignore[method-assign]
        return_value=[_link(uuid4(), "definitely-not-a-real-type")]
    )
    calls: list[dict[str, Any]] = []

    async def fake_link(**kwargs: Any) -> Any:
        calls.append(kwargs)
        return MagicMock()

    with patch.object(svc, "link_session_to_task", new=fake_link):
        await svc.propagate_sessions_to_subtask(uuid4(), uuid4(), uuid4())
    assert len(calls) == 1
    assert calls[0]["relationship_type"] == SessionTaskRelationshipType.DISCUSSION
