"""Tests for Phase 21b — arr_monitor Cloud Function."""

from __future__ import annotations

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

import pytest

_PATCH_DB = "backend.ops.arr_monitor.get_db"
_PATCH_PADDLE_KEY = "backend.ops.arr_monitor._get_paddle_api_key"
_PATCH_ALERT = "backend.ops.arr_monitor.send_alert_email"


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


def _make_sub(tier: str) -> dict[str, Any]:
    return {"custom_data": {"tier": tier}, "items": []}


def _build_arr_db(
    latest_data: dict[str, Any] | None = None,
) -> MagicMock:
    db = MagicMock()
    set_mock = AsyncMock()

    def collection_fn(name: str) -> Any:
        coll = MagicMock()
        if name == "founder_metrics":
            doc_ref = MagicMock()
            doc_ref.set = set_mock

            # .get() for reading latest
            latest_doc = MagicMock()
            if latest_data is not None:
                latest_doc.exists = True
                latest_doc.to_dict.return_value = latest_data
            else:
                latest_doc.exists = False
                latest_doc.to_dict.return_value = None
            doc_ref.get = AsyncMock(return_value=latest_doc)

            coll.document = MagicMock(return_value=doc_ref)
        return coll

    db.collection = MagicMock(side_effect=collection_fn)
    db._set_mock = set_mock
    return db


# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------


class TestARRMonitor:
    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_DB)
    @patch(_PATCH_PADDLE_KEY, new_callable=AsyncMock, return_value="test-key")
    async def test_compute_arr(
        self,
        _mock_key: AsyncMock,
        mock_db: MagicMock,
        _mock_alert: AsyncMock,
    ) -> None:
        """3 Pro + 2 Team → correct MRR/ARR."""
        db = _build_arr_db()
        mock_db.return_value = db

        subs = [_make_sub("pro")] * 3 + [_make_sub("team")] * 2
        resp = MagicMock()
        resp.status_code = 200
        resp.json.return_value = {
            "data": subs,
            "meta": {"pagination": {"next": ""}},
        }
        resp.raise_for_status = MagicMock()

        mock_client = AsyncMock()
        mock_client.get = AsyncMock(return_value=resp)
        mock_client.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client.__aexit__ = AsyncMock(return_value=False)

        with patch(
            "backend.ops.arr_monitor.httpx.AsyncClient",
            return_value=mock_client,
        ):
            from backend.ops.arr_monitor import arr_monitor

            result = await arr_monitor.__wrapped__(None)

        # Pro: 3 × $599/12, Team: 2 × $1999/12
        expected_mrr = round(3 * 599 / 12 + 2 * 1999 / 12, 2)
        assert result["mrr"] == expected_mrr
        # ARR = MRR × 12 (computed from unrounded MRR internally)
        assert abs(result["arr"] - expected_mrr * 12) < 1.0

    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_DB)
    @patch(_PATCH_PADDLE_KEY, new_callable=AsyncMock, return_value="test-key")
    async def test_soc2_alert_threshold(
        self,
        _mock_key: AsyncMock,
        mock_db: MagicMock,
        mock_alert: AsyncMock,
    ) -> None:
        """MRR >= $8,333 sends SOC 2 alert."""
        db = _build_arr_db()
        mock_db.return_value = db

        # Enterprise: $24,999/12 = $2,083.25 per sub. Need 4 → $8,333
        subs = [_make_sub("enterprise")] * 4
        resp = MagicMock()
        resp.status_code = 200
        resp.json.return_value = {
            "data": subs,
            "meta": {"pagination": {"next": ""}},
        }
        resp.raise_for_status = MagicMock()

        mock_client = AsyncMock()
        mock_client.get = AsyncMock(return_value=resp)
        mock_client.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client.__aexit__ = AsyncMock(return_value=False)

        with patch(
            "backend.ops.arr_monitor.httpx.AsyncClient",
            return_value=mock_client,
        ):
            from backend.ops.arr_monitor import arr_monitor

            await arr_monitor.__wrapped__(None)

        # Alert should have been sent
        mock_alert.assert_called_once()
        call_kwargs = mock_alert.call_args
        assert "SOC 2" in call_kwargs[1]["subject"] or "SOC 2" in call_kwargs[0][0]

    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_DB)
    @patch(_PATCH_PADDLE_KEY, new_callable=AsyncMock, return_value="test-key")
    async def test_soc2_alert_one_time(
        self,
        _mock_key: AsyncMock,
        mock_db: MagicMock,
        mock_alert: AsyncMock,
    ) -> None:
        """Alert not re-sent if already flagged."""
        db = _build_arr_db(latest_data={"soc2_alert_sent": True})
        mock_db.return_value = db

        subs = [_make_sub("enterprise")] * 4
        resp = MagicMock()
        resp.status_code = 200
        resp.json.return_value = {
            "data": subs,
            "meta": {"pagination": {"next": ""}},
        }
        resp.raise_for_status = MagicMock()

        mock_client = AsyncMock()
        mock_client.get = AsyncMock(return_value=resp)
        mock_client.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client.__aexit__ = AsyncMock(return_value=False)

        with patch(
            "backend.ops.arr_monitor.httpx.AsyncClient",
            return_value=mock_client,
        ):
            from backend.ops.arr_monitor import arr_monitor

            await arr_monitor.__wrapped__(None)

        # Alert should NOT have been called
        mock_alert.assert_not_called()

    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_DB)
    @patch(
        _PATCH_PADDLE_KEY,
        new_callable=AsyncMock,
        side_effect=Exception("Secret Manager error"),
    )
    async def test_paddle_failure(
        self,
        _mock_key: AsyncMock,
        mock_db: MagicMock,
        _mock_alert: AsyncMock,
    ) -> None:
        """Logs error, wrapper returns 200."""
        db = _build_arr_db()
        mock_db.return_value = db

        from backend.ops.arr_monitor import arr_monitor

        result = await arr_monitor(None)
        assert result["status"] == "error"
