"""Tests for Phase 21d — founder dashboard routes and admin auth."""

from __future__ import annotations

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

import pytest
from backend.dashboard.founder.context import (
    FounderDashboardContext,
)
from backend.dashboard.founder.routes import (
    _build_founder_context,
    _determine_scan_trend,
    _determine_system_health,
    _is_stale,
)

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


def _make_doc(
    data: dict[str, Any] | None,
    *,
    exists: bool = True,
) -> MagicMock:
    doc = MagicMock()
    doc.exists = exists
    doc.to_dict.return_value = data
    return doc


def _fake_db(
    metrics_data: dict[str, Any] | None = None,
    *,
    admin_exists: bool = True,
    issues_data: dict[str, Any] | None = None,
) -> MagicMock:
    """Build a fake Firestore DB for metrics and admin lookups."""
    db = MagicMock()

    metrics_doc = _make_doc(metrics_data, exists=metrics_data is not None)
    issues_doc = _make_doc(issues_data, exists=issues_data is not None)
    admin_doc = _make_doc({"email": "test@complyform.dev"}, exists=admin_exists)

    def _collection(name: str) -> MagicMock:
        col = MagicMock()
        if name == "founder_metrics":
            col.document = MagicMock(
                side_effect=lambda doc_id: _metrics_doc_router(
                    doc_id, metrics_doc, issues_doc
                )
            )
        elif name == "admins":
            doc_ref = MagicMock()
            doc_ref.get = AsyncMock(return_value=admin_doc)
            col.document = MagicMock(return_value=doc_ref)
        return col

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


def _metrics_doc_router(
    doc_id: str,
    metrics_doc: MagicMock,
    issues_doc: MagicMock,
) -> MagicMock:
    doc_ref = MagicMock()
    if doc_id == "github_issues":
        doc_ref.get = AsyncMock(return_value=issues_doc)
    else:
        doc_ref.get = AsyncMock(return_value=metrics_doc)
    return doc_ref


def _make_customer_context() -> MagicMock:
    """Build a mock CustomerContext for admin tests."""
    ctx = MagicMock()
    ctx.email = "admin@complyform.dev"
    ctx.customer_id = "cust_123"
    ctx.tier = "team"
    ctx.role = "owner"
    ctx.member_email = "admin@complyform.dev"
    ctx.features = ["hosted_dashboard"]
    ctx.consent_accepted = True
    ctx.tos_version = "1.0"
    ctx.privacy_version = "1.0"
    ctx.cancellation_pending = False
    ctx.subscription_expires_at = ""
    ctx.project_limit = 1
    ctx.batch_limit = 10
    return ctx


def _make_request() -> MagicMock:
    request = MagicMock()
    request.cookies = {"cf_session": "encrypted_cookie"}
    request.headers = {}
    request.client = MagicMock()
    request.client.host = "127.0.0.1"
    request.state = MagicMock()
    return request


# Patch paths
_PATCH_DB = "backend.shared.firestore.get_db"
_PATCH_AUTH = "backend.dashboard.middleware.require_dashboard_auth"


# ---------------------------------------------------------------------------
# Unit tests: context builders
# ---------------------------------------------------------------------------


class TestIsStale:
    def test_none_computed_at(self) -> None:
        assert _is_stale(None) is True

    def test_empty_string(self) -> None:
        assert _is_stale("") is True

    def test_recent(self) -> None:
        recent = datetime.now(UTC).isoformat()
        assert _is_stale(recent) is False

    def test_old(self) -> None:
        old = (datetime.now(UTC) - timedelta(hours=49)).isoformat()
        assert _is_stale(old) is True

    def test_exactly_48h(self) -> None:
        borderline = (datetime.now(UTC) - timedelta(hours=48, seconds=1)).isoformat()
        assert _is_stale(borderline) is True

    def test_invalid_format(self) -> None:
        assert _is_stale("not-a-date") is True


class TestDetermineSystemHealth:
    def test_green(self) -> None:
        assert _determine_system_health({}) == "green"

    def test_red_high_issues(self) -> None:
        assert _determine_system_health({"open_issues_p1_p2": 5}) == "red"

    def test_red_high_cost(self) -> None:
        assert _determine_system_health({"cost_to_revenue_ratio": 12}) == "red"

    def test_red_high_bounce(self) -> None:
        assert _determine_system_health({"postmark_bounce_rate": 6}) == "red"

    def test_yellow_moderate_issues(self) -> None:
        assert _determine_system_health({"open_issues_p1_p2": 2}) == "yellow"

    def test_yellow_moderate_cost(self) -> None:
        assert _determine_system_health({"cost_to_revenue_ratio": 7}) == "yellow"


class TestDetermineScanTrend:
    def test_stable_when_none(self) -> None:
        assert _determine_scan_trend({}) == "stable"

    def test_up(self) -> None:
        assert (
            _determine_scan_trend({"scan_frequency_7d": 50, "scan_frequency_30d": 100})
            == "up"
        )

    def test_declining(self) -> None:
        assert (
            _determine_scan_trend({"scan_frequency_7d": 10, "scan_frequency_30d": 200})
            == "declining"
        )

    def test_stable(self) -> None:
        assert (
            _determine_scan_trend({"scan_frequency_7d": 25, "scan_frequency_30d": 100})
            == "stable"
        )


class TestBuildFounderContext:
    def test_full_metrics(self) -> None:
        metrics: dict[str, Any] = {
            "arr": 12000.0,
            "mrr": 1000.0,
            "total_customers": 10,
            "nrr": 105.0,
            "logo_churn_30d": 2.5,
            "burn_multiple": 1.2,
            "cac_payback_days": 60,
            "scan_frequency_7d": 25,
            "scan_frequency_30d": 100,
            "open_issues_p1_p2": 1,
            "corpus_triples": 250,
            "cost_to_revenue_ratio": 3.5,
            "computed_at": datetime.now(UTC).isoformat(),
        }
        ctx = _build_founder_context(metrics)
        assert isinstance(ctx, FounderDashboardContext)
        assert ctx.top_row.arr == 12000.0
        assert ctx.growth_row.nrr == 105.0
        assert ctx.health_row.corpus_triples == 250
        assert ctx.is_stale is False

    def test_empty_metrics(self) -> None:
        ctx = _build_founder_context({})
        assert ctx.top_row.arr is None
        assert ctx.is_stale is True

    @pytest.mark.parametrize(
        "computed_at, expected_stale",
        [
            (None, True),
            (
                (datetime.now(UTC) - timedelta(hours=49)).isoformat(),
                True,
            ),
            (datetime.now(UTC).isoformat(), False),
        ],
    )
    def test_staleness_detection(
        self,
        computed_at: str | None,
        expected_stale: bool,
    ) -> None:
        metrics: dict[str, Any] = {"computed_at": computed_at}
        ctx = _build_founder_context(metrics)
        assert ctx.is_stale is expected_stale


class TestContextWithNullFields:
    """Paddle down → null metrics render gracefully."""

    def test_all_none_fields(self) -> None:
        ctx = _build_founder_context(
            {
                "arr": None,
                "mrr": None,
                "total_customers": None,
                "nrr": None,
                "logo_churn_30d": None,
                "burn_multiple": None,
                "cac_payback_days": None,
                "open_issues_p1_p2": None,
                "corpus_triples": None,
                "cost_to_revenue_ratio": None,
                "computed_at": None,
            }
        )
        assert ctx.top_row.arr is None
        assert ctx.top_row.system_health == "green"
        assert ctx.growth_row.burn_multiple is None
        assert ctx.health_row.scan_frequency_trend == "stable"
        assert ctx.is_stale is True


# ---------------------------------------------------------------------------
# Integration tests: route handlers (mocked Firestore + auth)
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_founder_index_admin() -> None:
    """Admin email → 200 with metrics."""
    metrics = {
        "arr": 5000.0,
        "mrr": 416.67,
        "total_customers": 3,
        "computed_at": datetime.now(UTC).isoformat(),
    }
    db = _fake_db(metrics, admin_exists=True)
    ctx = _make_customer_context()

    with (
        patch(_PATCH_DB, return_value=db),
        patch(_PATCH_AUTH, new_callable=AsyncMock, return_value=ctx),
    ):
        from backend.dashboard.founder.routes import (
            founder_index,
        )

        request = _make_request()
        response = await founder_index(request)
        assert response.status_code == 200
        body = response.body.decode()
        assert "Founder Dashboard" in body
        assert "$5,000" in body


@pytest.mark.asyncio
async def test_founder_index_non_admin() -> None:
    """Non-admin → 404 (not 403)."""
    db = _fake_db(None, admin_exists=False)
    ctx = _make_customer_context()

    with (
        patch(_PATCH_DB, return_value=db),
        patch(_PATCH_AUTH, new_callable=AsyncMock, return_value=ctx),
    ):
        from backend.dashboard.middleware.admin_auth import (
            require_admin,
        )
        from fastapi import HTTPException

        request = _make_request()

        with pytest.raises(HTTPException) as exc_info:
            await require_admin(request)
        assert exc_info.value.status_code == 404


@pytest.mark.asyncio
async def test_founder_index_unauthenticated() -> None:
    """Unauthenticated → 404 (hides route)."""
    from backend.dashboard.middleware import DashboardAuthError

    with patch(
        _PATCH_AUTH,
        new_callable=AsyncMock,
        side_effect=DashboardAuthError(redirect=True),
    ):
        from backend.dashboard.middleware.admin_auth import (
            require_admin,
        )
        from fastapi import HTTPException

        request = _make_request()
        with pytest.raises(HTTPException) as exc_info:
            await require_admin(request)
        assert exc_info.value.status_code == 404


@pytest.mark.asyncio
async def test_founder_health_partial() -> None:
    """Returns health drilldown HTML."""
    metrics = {
        "postmark_delivery_rate": 99.1,
        "postmark_bounce_rate": 0.5,
        "postmark_spam_rate": 0.01,
        "activation_at_risk_count": 2,
    }
    issues = {
        "issues_by_tier": {"pro": 3, "team": 1},
        "oldest_unresolved": {
            "number": 42,
            "title": "Fix auth bug",
            "created_at": "2026-01-15",
        },
    }
    db = _fake_db(metrics, admin_exists=True, issues_data=issues)

    with patch(_PATCH_DB, return_value=db):
        from backend.dashboard.founder.routes import (
            founder_health,
        )

        request = _make_request()
        response = await founder_health(request)
        assert response.status_code == 200
        body = response.body.decode()
        assert "Health Drilldown" in body
        assert "99.1%" in body
        assert "#42" in body


@pytest.mark.asyncio
async def test_founder_growth_partial() -> None:
    """Returns growth drilldown HTML."""
    metrics = {
        "mrr": 1000.0,
        "total_customers": 5,
        "revenue_by_tier": {"pro": 300, "team": 700},
        "nps_score": 45.0,
        "scan_frequency_7d": 20,
        "scan_frequency_30d": 80,
    }
    db = _fake_db(metrics, admin_exists=True)

    with patch(_PATCH_DB, return_value=db):
        from backend.dashboard.founder.routes import (
            founder_growth,
        )

        request = _make_request()
        response = await founder_growth(request)
        assert response.status_code == 200
        body = response.body.decode()
        assert "Growth Drilldown" in body


@pytest.mark.asyncio
async def test_founder_corpus_partial() -> None:
    """Returns corpus drilldown HTML."""
    metrics = {"corpus_triples": 350}
    db = _fake_db(metrics, admin_exists=True)

    with patch(_PATCH_DB, return_value=db):
        from backend.dashboard.founder.routes import (
            founder_corpus,
        )

        request = _make_request()
        response = await founder_corpus(request)
        assert response.status_code == 200
        body = response.body.decode()
        assert "Corpus Drilldown" in body
        assert "350" in body
        assert "Building" in body  # 100-499 = yellow
