"""Tests for Phase 21c — newsletter_draft 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.newsletter_draft.get_db"
_PATCH_SEND = "backend.ops.newsletter_draft.send_template_email"
_PATCH_ALERT = "backend.ops._shared.send_alert_email"


# ---------------------------------------------------------------------------
# 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 _FakeCollection:
    def __init__(self, docs: list[MagicMock] | None = None) -> None:
        self._docs = docs or []
        self._doc_refs: dict[str, MagicMock] = {}

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

    def document(self, doc_id: str) -> MagicMock:
        if doc_id not in self._doc_refs:
            ref = MagicMock()
            ref.set = AsyncMock()
            self._doc_refs[doc_id] = ref
        return self._doc_refs[doc_id]


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 _news_item(
    title: str,
    category: str,
    days_ago: int = 3,
) -> dict[str, Any]:
    return {
        "source": "test",
        "title": title,
        "url": f"https://example.com/{title.lower().replace(' ', '-')}",
        "summary": f"Summary of {title}",
        "category": category,
        "published_at": (datetime.now(UTC) - timedelta(days=days_ago)).isoformat(),
    }


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


class TestNewsletterDraft:
    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_draft_assembled(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """Items grouped by category, Markdown generated."""
        items = [
            _news_item("New SOC 2 Rule", "regulation_updates"),
            _news_item("Checkov v3", "tool_releases"),
            _news_item("Big Breach", "breach_news"),
        ]
        docs = [_make_doc(f"item{i}", item) for i, item in enumerate(items)]

        content_col = _FakeCollection(docs)
        drafts_col = _FakeCollection()

        def _router(name: str) -> Any:
            if name == "newsletter_content":
                return content_col
            if name == "newsletter_drafts":
                return drafts_col
            return _FakeCollection()

        db = MagicMock()
        db.collection = _router
        mock_db.return_value = db

        from backend.ops.newsletter_draft import newsletter_draft

        result = await newsletter_draft(MagicMock())
        assert result == {"status": "ok", "function": "complyform-newsletter-draft"}

        # Check draft was written
        today = datetime.now(UTC).strftime("%Y-%m-%d")
        draft_ref = drafts_col.document(today)
        assert draft_ref.set.called
        write_data = draft_ref.set.call_args.args[0]
        assert "markdown_body" in write_data
        assert write_data["item_count"] == 3
        # Check categories in Markdown
        md = write_data["markdown_body"]
        assert "## Regulation Updates" in md
        assert "## Tool Releases" in md
        assert "## Industry News" in md

    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_no_content_skips(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """No recent items → skip."""
        content_col = _FakeCollection([])
        drafts_col = _FakeCollection()

        def _router(name: str) -> Any:
            if name == "newsletter_content":
                return content_col
            if name == "newsletter_drafts":
                return drafts_col
            return _FakeCollection()

        db = MagicMock()
        db.collection = _router
        mock_db.return_value = db

        from backend.ops.newsletter_draft import newsletter_draft

        result = await newsletter_draft(MagicMock())
        assert result == {"status": "ok", "function": "complyform-newsletter-draft"}

        # No send
        mock_send.assert_not_called()

    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_writes_firestore(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """Draft written to newsletter_drafts/{date}."""
        items = [_news_item("Item 1", "tool_releases", days_ago=2)]
        docs = [_make_doc("item0", items[0])]

        content_col = _FakeCollection(docs)
        drafts_col = _FakeCollection()

        def _router(name: str) -> Any:
            if name == "newsletter_content":
                return content_col
            if name == "newsletter_drafts":
                return drafts_col
            return _FakeCollection()

        db = MagicMock()
        db.collection = _router
        mock_db.return_value = db

        from backend.ops.newsletter_draft import newsletter_draft

        await newsletter_draft(MagicMock())

        today = datetime.now(UTC).strftime("%Y-%m-%d")
        draft_ref = drafts_col.document(today)
        assert draft_ref.set.called
        data = draft_ref.set.call_args.args[0]
        assert data["sent"] is False
        assert "generated_at" in data

    @pytest.mark.asyncio
    @patch(_PATCH_ALERT, new_callable=AsyncMock)
    @patch(_PATCH_SEND, new_callable=AsyncMock, return_value=True)
    @patch(_PATCH_DB)
    async def test_preview_sent(
        self,
        mock_db: MagicMock,
        mock_send: AsyncMock,
        mock_alert: AsyncMock,
    ) -> None:
        """Preview email sent to founder."""
        items = [_news_item("Item 1", "regulation_updates")]
        docs = [_make_doc("item0", items[0])]

        content_col = _FakeCollection(docs)
        drafts_col = _FakeCollection()

        def _router(name: str) -> Any:
            if name == "newsletter_content":
                return content_col
            if name == "newsletter_drafts":
                return drafts_col
            return _FakeCollection()

        db = MagicMock()
        db.collection = _router
        mock_db.return_value = db

        from backend.ops.newsletter_draft import newsletter_draft

        await newsletter_draft(MagicMock())

        # Preview sent
        assert mock_send.called
        call = mock_send.call_args
        assert call.kwargs.get("template_alias") == "newsletter-preview"
