"""Tests for Phase 21c — onboarding_drip Cloud Function."""

from __future__ import annotations

from datetime import UTC, datetime, timedelta
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

_PATCH_DB = "backend.ops.onboarding_drip.get_db"
_PATCH_SEND = "backend.ops.onboarding_drip.send_template_email"
_PATCH_KEY = "backend.ops.onboarding_drip._get_nps_signing_key"
_PATCH_ALERT = "backend.ops._shared.send_alert_email"
_SIGNING_KEY = "test-signing-key-for-nps"


def _get_templates(mock_send: AsyncMock) -> list[str]:
    """Extract template aliases from send_template_email calls."""
    result: list[str] = []
    for c in mock_send.call_args_list:
        alias = c.kwargs.get("template_alias", "")
        if not alias and len(c.args) > 1:
            alias = c.args[1]
        result.append(alias)
    return result


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


class _FakeAsyncIter:
    def __init__(self, items: list[Any]) -> None:
        self._items = list(items)
        self._index = 0

    def __aiter__(self) -> _FakeAsyncIter:
        return self

    async def __anext__(self) -> Any:
        if self._index >= len(self._items):
            raise StopAsyncIteration
        item = self._items[self._index]
        self._index += 1
        return item


class _FakeQuery:
    def __init__(self, docs: list[MagicMock]) -> None:
        self._docs = docs

    def where(self, *args: Any, **kwargs: Any) -> _FakeQuery:
        return self

    def stream(self) -> _FakeAsyncIter:
        return _FakeAsyncIter(self._docs)


class _FakeCollection:
    """Fake Firestore collection with subcollection support."""

    def __init__(self, docs: list[MagicMock] | None = None) -> None:
        self._docs = docs or []
        self._subcollections: dict[str, _FakeCollection] = {}
        self._documents: dict[str, MagicMock] = {}

    def where(self, *args: Any, **kwargs: Any) -> _FakeCollection:
        return self

    def stream(self) -> _FakeAsyncIter:
        return _FakeAsyncIter(self._docs)

    def document(self, doc_id: str) -> MagicMock:
        if doc_id in self._documents:
            return self._documents[doc_id]
        doc_ref = MagicMock()
        doc_ref.set = AsyncMock()
        snap = MagicMock()
        snap.exists = False
        snap.to_dict.return_value = {}
        doc_ref.get = AsyncMock(return_value=snap)
        doc_ref.collection = lambda name: self._subcollections.get(
            name,
            _FakeCollection(),
        )
        self._documents[doc_id] = doc_ref
        return doc_ref


def _make_doc(doc_id: str, data: dict[str, Any]) -> MagicMock:
    doc = MagicMock()
    doc.id = doc_id
    doc.to_dict.return_value = data
    return doc


def _license_doc(
    customer_id: str,
    email: str,
    tier: str = "pro",
    days_ago: int = 2,
) -> MagicMock:
    created = (datetime.now(UTC) - timedelta(days=days_ago)).isoformat()
    return _make_doc(
        f"lk_{customer_id}",
        {
            "customer_id": customer_id,
            "email": email,
            "tier": tier,
            "status": "active",
            "created_at": created,
        },
    )


def _setup_db(
    license_docs: list[MagicMock],
    lifecycle_data: dict[str, dict[str, Any]] | None = None,
    customer_data: dict[str, dict[str, Any]] | None = None,
    project_docs: dict[str, list[MagicMock]] | None = None,
) -> MagicMock:
    """Build a fake Firestore DB with multiple collection support."""
    lifecycle_data = lifecycle_data or {}
    customer_data = customer_data or {}
    project_docs = project_docs or {}

    db = MagicMock()

    lk_collection = _FakeCollection(license_docs)
    lifecycle_collection = _FakeCollection()
    customers_collection = _FakeCollection()

    def _collection_router(name: str) -> Any:
        if name == "license_keys":
            return lk_collection
        if name == "customer_lifecycle":
            return lifecycle_collection
        if name == "customers":
            return customers_collection
        return _FakeCollection()

    db.collection = _collection_router

    # Setup lifecycle documents
    for cid, ldata in lifecycle_data.items():
        doc_ref = lifecycle_collection.document(cid)
        snap = MagicMock()
        snap.exists = True
        snap.to_dict.return_value = ldata
        doc_ref.get = AsyncMock(return_value=snap)

    # Setup customer documents with projects subcollection
    for cid, cdata in customer_data.items():
        doc_ref = customers_collection.document(cid)
        snap = MagicMock()
        snap.exists = True
        snap.to_dict.return_value = cdata
        doc_ref.get = AsyncMock(return_value=snap)
        # Subcollections
        pdocs = project_docs.get(cid, [])
        doc_ref.collection = lambda name, _d=pdocs: _FakeCollection(_d)

    return db


# ---------------------------------------------------------------------------
# A) Onboarding sequence tests
# ---------------------------------------------------------------------------


class TestOnboardingDrip:
    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_KEY, new_callable=AsyncMock, return_value=_SIGNING_KEY)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_nudge_sent_no_scan_24hr(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_key: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """Customer with no scan 24hr post-activation gets nudge."""
        lk = _license_doc("cust1", "a@b.com", days_ago=2)
        db = _setup_db([lk], customer_data={"cust1": {"tier": "pro"}})
        mock_db.return_value = db

        from backend.ops.onboarding_drip import onboarding_drip

        result = await onboarding_drip(MagicMock())
        assert result == {"status": "ok", "function": "complyform-onboarding-drip"}

        # Check that nudge email was sent
        templates = _get_templates(mock_send)
        assert "onboarding-first-scan-nudge" in templates

    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_KEY, new_callable=AsyncMock, return_value=_SIGNING_KEY)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_skip_nudge_early_activation(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_key: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """Customer who scanned within 24hr skips nudge, gets activation-success."""
        lk = _license_doc("cust1", "a@b.com", days_ago=2)
        scan_time = (datetime.now(UTC) - timedelta(hours=20)).isoformat()
        project_doc = _make_doc("proj1", {"last_scan": scan_time})
        db = _setup_db(
            [lk],
            customer_data={"cust1": {"tier": "pro"}},
            project_docs={"cust1": [project_doc]},
        )
        mock_db.return_value = db

        from backend.ops.onboarding_drip import onboarding_drip

        await onboarding_drip(MagicMock())

        templates = _get_templates(mock_send)
        assert "onboarding-activation-success" in templates
        assert "onboarding-first-scan-nudge" not in templates

    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_KEY, new_callable=AsyncMock, return_value=_SIGNING_KEY)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_first_scan_detected(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_key: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """Nudge-sent customer who later scans gets activation-success."""
        lk = _license_doc("cust1", "a@b.com", days_ago=3)
        scan_time = (datetime.now(UTC) - timedelta(hours=10)).isoformat()
        project_doc = _make_doc("proj1", {"last_scan": scan_time})
        db = _setup_db(
            [lk],
            lifecycle_data={"cust1": {"milestone_state": "nudge_sent"}},
            customer_data={"cust1": {"tier": "pro"}},
            project_docs={"cust1": [project_doc]},
        )
        mock_db.return_value = db

        from backend.ops.onboarding_drip import onboarding_drip

        await onboarding_drip(MagicMock())

        templates = _get_templates(mock_send)
        assert "onboarding-activation-success" in templates

    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_KEY, new_callable=AsyncMock, return_value=_SIGNING_KEY)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_remediate_explainer(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_key: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """3d post-scan with no remediate → remediate-explainer email."""
        lk = _license_doc("cust1", "a@b.com", days_ago=7)
        scan_time = (datetime.now(UTC) - timedelta(days=5)).isoformat()
        project_doc = _make_doc("proj1", {"last_scan": scan_time})
        db = _setup_db(
            [lk],
            lifecycle_data={
                "cust1": {
                    "milestone_state": "first_scan",
                    "first_scan_at": (
                        datetime.now(UTC) - timedelta(days=5)
                    ).isoformat(),
                },
            },
            customer_data={"cust1": {"tier": "pro"}},
            project_docs={"cust1": [project_doc]},
        )
        mock_db.return_value = db

        from backend.ops.onboarding_drip import onboarding_drip

        await onboarding_drip(MagicMock())

        templates = _get_templates(mock_send)
        assert "onboarding-remediate-explainer" in templates

    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_KEY, new_callable=AsyncMock, return_value=_SIGNING_KEY)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_day7_checkin_inactive(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_key: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """Day 7+, no scan in 5 days → day7-checkin email."""
        lk = _license_doc("cust1", "a@b.com", days_ago=8)
        # Last scan was 6 days ago (outside 5-day window)
        old_scan = (datetime.now(UTC) - timedelta(days=6)).isoformat()
        project_doc = _make_doc("proj1", {"last_scan": old_scan})
        db = _setup_db(
            [lk],
            lifecycle_data={"cust1": {"milestone_state": "remediate_explained"}},
            customer_data={"cust1": {"tier": "pro"}},
            project_docs={"cust1": [project_doc]},
        )
        mock_db.return_value = db

        from backend.ops.onboarding_drip import onboarding_drip

        await onboarding_drip(MagicMock())

        templates = _get_templates(mock_send)
        assert "onboarding-day7-checkin" in templates

    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_KEY, new_callable=AsyncMock, return_value=_SIGNING_KEY)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_day7_checkin_skipped_active(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_key: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """Day 7+, scanned within 5 days → skip day7-checkin."""
        lk = _license_doc("cust1", "a@b.com", days_ago=8)
        recent_scan = (datetime.now(UTC) - timedelta(days=2)).isoformat()
        project_doc = _make_doc("proj1", {"last_scan": recent_scan})
        db = _setup_db(
            [lk],
            lifecycle_data={"cust1": {"milestone_state": "remediate_explained"}},
            customer_data={"cust1": {"tier": "pro"}},
            project_docs={"cust1": [project_doc]},
        )
        mock_db.return_value = db

        from backend.ops.onboarding_drip import onboarding_drip

        await onboarding_drip(MagicMock())

        templates = _get_templates(mock_send)
        assert "onboarding-day7-checkin" not in templates

    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_KEY, new_callable=AsyncMock, return_value=_SIGNING_KEY)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_day14_success(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_key: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """Day 14 with scan + assess → day14-success email."""
        lk = _license_doc("cust1", "a@b.com", days_ago=14)
        project_doc = _make_doc(
            "proj1",
            {
                "last_scan": datetime.now(UTC).isoformat(),
                "last_assess": datetime.now(UTC).isoformat(),
            },
        )
        db = _setup_db(
            [lk],
            lifecycle_data={"cust1": {"milestone_state": "day7_checkin"}},
            customer_data={"cust1": {"tier": "pro"}},
            project_docs={"cust1": [project_doc]},
        )
        mock_db.return_value = db

        from backend.ops.onboarding_drip import onboarding_drip

        await onboarding_drip(MagicMock())

        templates = _get_templates(mock_send)
        assert "onboarding-day14-success" in templates

    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_KEY, new_callable=AsyncMock, return_value=_SIGNING_KEY)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_sequence_termination_day14(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_key: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """Day 14+ without scan+assess → completed (no email)."""
        lk = _license_doc("cust1", "a@b.com", days_ago=15)
        db = _setup_db(
            [lk],
            lifecycle_data={"cust1": {"milestone_state": "nudge_sent"}},
            customer_data={"cust1": {"tier": "pro"}},
        )
        mock_db.return_value = db

        from backend.ops.onboarding_drip import onboarding_drip

        await onboarding_drip(MagicMock())

        # Lifecycle should be set to completed (checked via set call)
        lifecycle_ref = db.collection("customer_lifecycle").document("cust1")
        set_call = lifecycle_ref.set.call_args
        assert set_call is not None
        updates = set_call.args[0]
        assert updates.get("milestone_state") == "completed"

    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_KEY, new_callable=AsyncMock, return_value=_SIGNING_KEY)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_max_one_email_per_day(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_key: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """Customer who received email < 24hr ago gets no new email."""
        recent_email = (datetime.now(UTC) - timedelta(hours=12)).isoformat()
        lk = _license_doc("cust1", "a@b.com", days_ago=2)
        db = _setup_db(
            [lk],
            lifecycle_data={
                "cust1": {
                    "milestone_state": "activated",
                    "last_email_at": recent_email,
                },
            },
            customer_data={"cust1": {"tier": "pro"}},
        )
        mock_db.return_value = db

        from backend.ops.onboarding_drip import onboarding_drip

        await onboarding_drip(MagicMock())

        # No onboarding emails sent (NPS/expiry may still fire)
        templates = _get_templates(mock_send)
        onboarding_templates = {
            "onboarding-first-scan-nudge",
            "onboarding-activation-success",
            "onboarding-remediate-explainer",
            "onboarding-day7-checkin",
            "onboarding-day14-success",
        }
        assert not (set(templates) & onboarding_templates)

    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_KEY, new_callable=AsyncMock, return_value=_SIGNING_KEY)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_community_excluded(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_key: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """Community tier customers get no emails."""
        lk = _license_doc("cust1", "a@b.com", tier="community", days_ago=2)
        db = _setup_db([lk], customer_data={"cust1": {"tier": "community"}})
        mock_db.return_value = db

        from backend.ops.onboarding_drip import onboarding_drip

        await onboarding_drip(MagicMock())

        mock_send.assert_not_called()


# ---------------------------------------------------------------------------
# B) NPS milestones
# ---------------------------------------------------------------------------


class TestNPSMilestones:
    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_KEY, new_callable=AsyncMock, return_value=_SIGNING_KEY)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_nps_30_day(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_key: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """Day 30 → nps-survey sent."""
        lk = _license_doc("cust1", "a@b.com", days_ago=31)
        db = _setup_db(
            [lk],
            lifecycle_data={"cust1": {"milestone_state": "completed"}},
            customer_data={"cust1": {"tier": "pro"}},
        )
        mock_db.return_value = db

        from backend.ops.onboarding_drip import onboarding_drip

        await onboarding_drip(MagicMock())

        templates = _get_templates(mock_send)
        assert "nps-survey" in templates

    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_KEY, new_callable=AsyncMock, return_value=_SIGNING_KEY)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_nps_90_day(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_key: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """Day 90 → nps-survey sent."""
        lk = _license_doc("cust1", "a@b.com", days_ago=91)
        db = _setup_db(
            [lk],
            lifecycle_data={
                "cust1": {
                    "milestone_state": "completed",
                    "nps_30_sent_at": datetime.now(UTC).isoformat(),
                },
            },
            customer_data={"cust1": {"tier": "pro"}},
        )
        mock_db.return_value = db

        from backend.ops.onboarding_drip import onboarding_drip

        await onboarding_drip(MagicMock())

        templates = _get_templates(mock_send)
        assert "nps-survey" in templates


# ---------------------------------------------------------------------------
# C) Expiry warnings
# ---------------------------------------------------------------------------


class TestExpiryWarnings:
    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_KEY, new_callable=AsyncMock, return_value=_SIGNING_KEY)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_expiry_30d_warning(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_key: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """30 days before expiry → license-expiry-warning."""
        expires_at = (datetime.now(UTC) + timedelta(days=25)).isoformat()
        lk = _license_doc("cust1", "a@b.com", days_ago=340)
        db = _setup_db(
            [lk],
            lifecycle_data={"cust1": {"milestone_state": "completed"}},
            customer_data={
                "cust1": {
                    "tier": "pro",
                    "subscription_expires_at": expires_at,
                    "cancellation_pending": False,
                },
            },
        )
        mock_db.return_value = db

        from backend.ops.onboarding_drip import onboarding_drip

        await onboarding_drip(MagicMock())

        templates = _get_templates(mock_send)
        assert "license-expiry-warning" in templates

    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_KEY, new_callable=AsyncMock, return_value=_SIGNING_KEY)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_expiry_7d_warning(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_key: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """7 days before expiry → license-expiry-warning with days_remaining=7."""
        expires_at = (datetime.now(UTC) + timedelta(days=5)).isoformat()
        lk = _license_doc("cust1", "a@b.com", days_ago=360)
        db = _setup_db(
            [lk],
            lifecycle_data={"cust1": {"milestone_state": "completed"}},
            customer_data={
                "cust1": {
                    "tier": "pro",
                    "subscription_expires_at": expires_at,
                    "cancellation_pending": False,
                },
            },
        )
        mock_db.return_value = db

        from backend.ops.onboarding_drip import onboarding_drip

        await onboarding_drip(MagicMock())

        # Find the call with days_remaining=7
        found_7d = False
        for call in mock_send.call_args_list:
            alias = call.kwargs.get("template_alias", "")
            if alias == "license-expiry-warning":
                model = call.kwargs.get("template_model", {})
                if model.get("days_remaining") == 7:
                    found_7d = True
        assert found_7d

    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_KEY, new_callable=AsyncMock, return_value=_SIGNING_KEY)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_expiry_skipped_cancellation_pending(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_key: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """No warning if cancellation_pending is true."""
        expires_at = (datetime.now(UTC) + timedelta(days=5)).isoformat()
        lk = _license_doc("cust1", "a@b.com", days_ago=360)
        db = _setup_db(
            [lk],
            lifecycle_data={"cust1": {"milestone_state": "completed"}},
            customer_data={
                "cust1": {
                    "tier": "pro",
                    "subscription_expires_at": expires_at,
                    "cancellation_pending": True,
                },
            },
        )
        mock_db.return_value = db

        from backend.ops.onboarding_drip import onboarding_drip

        await onboarding_drip(MagicMock())

        templates = _get_templates(mock_send)
        assert "license-expiry-warning" not in templates


# ---------------------------------------------------------------------------
# At-risk detection
# ---------------------------------------------------------------------------


class TestAtRiskDetection:
    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_KEY, new_callable=AsyncMock, return_value=_SIGNING_KEY)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_at_risk_detection(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_key: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """Day 7+, zero scans → at_risk=true in lifecycle."""
        lk = _license_doc("cust1", "a@b.com", days_ago=8)
        db = _setup_db(
            [lk],
            lifecycle_data={"cust1": {"milestone_state": "remediate_explained"}},
            customer_data={"cust1": {"tier": "pro"}},
        )
        mock_db.return_value = db

        from backend.ops.onboarding_drip import onboarding_drip

        await onboarding_drip(MagicMock())

        lifecycle_ref = db.collection("customer_lifecycle").document("cust1")
        set_call = lifecycle_ref.set.call_args
        assert set_call is not None
        updates = set_call.args[0]
        assert updates.get("at_risk") is True
