"""Tests for Phase 21a — dormancy_detection 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.dormancy_detection.get_db"


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


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


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 _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


def _build_dormancy_db(
    customers: list[MagicMock],
    projects_per_customer: dict[str, list[MagicMock]] | None = None,
) -> MagicMock:
    db = MagicMock()

    def collection_fn(name: str) -> Any:
        if name == "customers":
            mock_coll = MagicMock()
            mock_coll.where = MagicMock(return_value=_FakeQuery(customers))

            def coll_document(doc_id: str) -> Any:
                doc_ref = MagicMock()

                def sub_collection(sub_name: str) -> Any:
                    if sub_name == "projects":
                        projs = (
                            projects_per_customer.get(doc_id, [])
                            if projects_per_customer
                            else []
                        )
                        return _FakeQuery(projs)
                    return _FakeQuery([])

                doc_ref.collection = MagicMock(side_effect=sub_collection)
                return doc_ref

            mock_coll.document = MagicMock(side_effect=coll_document)
            return mock_coll
        return MagicMock()

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


def _make_project(proj_id: str, last_scan: str, status: str = "active") -> MagicMock:
    return _make_doc(proj_id, {"last_scan": last_scan, "status": status})


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


@pytest.mark.asyncio
@patch(_PATCH_DB)
async def test_mark_dormant(mock_db: MagicMock) -> None:
    """Project with last_scan > 90 days → marked dormant."""
    customer = _make_doc("cus_dorm", {"status": "active", "tier": "agency"})
    old_scan = (datetime.now(UTC) - timedelta(days=120)).isoformat()
    project = _make_project("proj_old", old_scan)

    db = _build_dormancy_db([customer], {"cus_dorm": [project]})
    mock_db.return_value = db

    from backend.ops.dormancy_detection import dormancy_detection

    result = await dormancy_detection.__wrapped__(None)
    assert result["projects_marked_dormant"] == 1
    project.reference.update.assert_called_once()
    call_args = project.reference.update.call_args[0][0]
    assert call_args["status"] == "dormant"
    assert "dormant_since" in call_args


@pytest.mark.asyncio
@patch(_PATCH_DB)
async def test_skip_recently_scanned(mock_db: MagicMock) -> None:
    """last_scan < 90 days → not marked dormant (filtered by query)."""
    customer = _make_doc("cus_active", {"status": "active", "tier": "agency"})
    # Query filters server-side, so no projects returned
    db = _build_dormancy_db([customer], {"cus_active": []})
    mock_db.return_value = db

    from backend.ops.dormancy_detection import dormancy_detection

    result = await dormancy_detection.__wrapped__(None)
    assert result["projects_marked_dormant"] == 0


@pytest.mark.asyncio
@patch(_PATCH_DB)
async def test_only_agency_enterprise(mock_db: MagicMock) -> None:
    """Team/Pro customers are skipped."""
    for tier in ("team", "pro", "community"):
        customer = _make_doc(f"cus_{tier}", {"status": "active", "tier": tier})
        db = _build_dormancy_db([customer])
        mock_db.return_value = db

        from backend.ops.dormancy_detection import dormancy_detection

        result = await dormancy_detection.__wrapped__(None)
        assert result["projects_marked_dormant"] == 0


@pytest.mark.asyncio
@patch(_PATCH_DB)
async def test_enterprise_unlimited_exempt(mock_db: MagicMock) -> None:
    customer = _make_doc(
        "cus_eu",
        {
            "status": "active",
            "tier": "enterprise_unlimited",
        },
    )
    old_scan = (datetime.now(UTC) - timedelta(days=120)).isoformat()
    project = _make_project("proj_eu", old_scan)
    db = _build_dormancy_db([customer], {"cus_eu": [project]})
    mock_db.return_value = db

    from backend.ops.dormancy_detection import dormancy_detection

    result = await dormancy_detection.__wrapped__(None)
    assert result["projects_marked_dormant"] == 0


@pytest.mark.asyncio
@patch(_PATCH_DB)
async def test_dormant_since_set(mock_db: MagicMock) -> None:
    """dormant_since timestamp is recorded when marking dormant."""
    customer = _make_doc("cus_ts", {"status": "active", "tier": "enterprise"})
    old_scan = (datetime.now(UTC) - timedelta(days=100)).isoformat()
    project = _make_project("proj_ts", old_scan)

    db = _build_dormancy_db([customer], {"cus_ts": [project]})
    mock_db.return_value = db

    from backend.ops.dormancy_detection import dormancy_detection

    await dormancy_detection.__wrapped__(None)
    call_args = project.reference.update.call_args[0][0]
    assert "dormant_since" in call_args
    # Verify it's a valid ISO timestamp
    datetime.fromisoformat(call_args["dormant_since"])
