"""What this deployment tells the CMS about itself.

The payload is stored in the CMS and rendered in a browser, so the test that
matters most here is the one asserting what is *not* in it. ADR-0002 keeps
CNICs, documents and phone numbers on the installation side, and the WhatsApp
allow-list — which the health check happily prints into its own detail string —
is a set of client phone numbers.
"""

from __future__ import annotations

import json

import pytest

from app.runtime import status_report


class FakeInboxPolicy:
    def __init__(self, configured=True, numbers=("+923001234567", "+923009999999")):
        self.is_configured = configured
        self._numbers = numbers

    def describe(self) -> str:
        # The real one does this too, which is exactly why nothing sends it.
        return ", ".join(self._numbers)


class FakeInbox:
    def __init__(self, configured=True, allowed=7, ignored=41):
        self.policy = FakeInboxPolicy(configured)
        self.allowed = allowed
        self.ignored = ignored


class FakeProvider:
    def __init__(self, name="meta", base_url="https://evo.test"):
        self.name = name
        self._base_url = base_url


class FakeContainer:
    def __init__(self, *, provider=None, inbox=None, connect=None, ocr_name="paddleocr"):
        self.whatsapp = provider if provider is not None else FakeProvider()
        self.inbox = inbox if inbox is not None else FakeInbox()
        self.connect = connect
        self.ocr = type("Ocr", (), {"name": ocr_name})()
        self.cms = FakeCms()
        self.memory = None
        self.settings = object()


class FakeCms:
    def __init__(self, fails=False):
        self.reports = []
        self.fails = fails

    def report_status(self, status):
        if self.fails:
            raise RuntimeError("connection refused")

        self.reports.append(status)

        return {"ok": True}


@pytest.fixture
def container(monkeypatch):
    """A container whose health check is stubbed to a known set of components."""
    from app.runtime import health

    def readiness(_container, _daemon=None):
        return health.Report([
            health.Component("cms", health.Status.OK, "authenticated as 'Intake'"),
            health.Component("database", health.Status.OK, "reachable"),
            health.Component("ocr", health.Status.DEGRADED, "not installed"),
            # The detail here names real numbers, in the real implementation.
            health.Component("whatsapp", health.Status.OK, "watching +923001234567"),
            health.Component("configuration", health.Status.OK, "loaded"),
        ])

    monkeypatch.setattr(health, "readiness", readiness)

    return FakeContainer()


class TestWhatIsReported:
    def test_it_reports_the_running_version(self, container):
        from app.release.version import VERSION

        assert status_report.build(container)["version"] == VERSION

    def test_it_reports_component_states(self, container):
        components = status_report.build(container)["components"]

        assert components["cms"] == "ok"
        assert components["ocr"] == "degraded"

    def test_it_reports_only_components_the_cms_accepts(self, container):
        """`configuration` is a real component and not one the CMS records.
        Sending it would suggest it was being kept."""
        assert "configuration" not in status_report.build(container)["components"]

    def test_it_reports_the_transport(self, container):
        whatsapp = status_report.build(container)["whatsapp"]

        assert whatsapp["provider"] == "meta"
        assert whatsapp["inbox_configured"] is True

    def test_it_reports_both_message_counts(self, container):
        """"It is not seeing my documents" and "it is reading everything it
        should ignore" look identical with only one of these."""
        whatsapp = status_report.build(container)["whatsapp"]

        assert whatsapp["messages_accepted"] == 7
        assert whatsapp["messages_ignored"] == 41

    def test_evolution_without_a_base_url_cannot_send(self, container):
        """It records instead of transmitting — a deployment that looks healthy
        and sends nothing."""
        container.whatsapp = FakeProvider(name="evolution", base_url="")

        assert status_report.build(container)["whatsapp"]["can_send"] is False

    def test_evolution_with_a_base_url_can_send(self, container):
        container.whatsapp = FakeProvider(name="evolution", base_url="https://evo.test")

        assert status_report.build(container)["whatsapp"]["can_send"] is True

    def test_meta_can_send_because_it_only_exists_with_credentials(self, container):
        assert status_report.build(container)["whatsapp"]["can_send"] is True

    def test_it_reports_the_ocr_engine(self, container):
        assert status_report.build(container)["ocr"]["engine"] == "paddleocr"

    def test_uptime_is_reported(self, container):
        assert status_report.build(container)["uptime_seconds"] >= 0


class TestWhatIsNeverReported:
    """ADR-0002. This payload is stored in the CMS and rendered in a browser."""

    def test_no_phone_number_reaches_the_cms(self, container):
        rendered = json.dumps(status_report.build(container))

        assert "923001234567" not in rendered
        assert "+92" not in rendered

    def test_component_details_are_dropped(self, container):
        """The whatsapp health detail is literally the allow-list. Only the
        state travels, never the text beside it."""
        rendered = json.dumps(status_report.build(container))

        assert "watching" not in rendered
        assert "authenticated as" not in rendered

    def test_it_carries_no_client_identifiers(self, container):
        """Named precisely rather than by keyword.

        A blunt "no key containing 'number'" check would also reject
        `connected_number`, which is the FIRM's own linked WhatsApp number —
        their intake line, which they must be shown so they can confirm they
        linked the account they meant to. ADR-0002 is about the client's data,
        not the customer's own configuration.
        """
        report = status_report.build(container)
        keys = set(report) | set(report.get("whatsapp", {}))

        for forbidden in ("cnic", "client", "sender", "chat", "text", "document", "allow"):
            assert not any(forbidden in key for key in keys), keys

    def test_the_connected_number_is_the_deployments_own(self, container):
        """The one number that IS allowed through, and why.

        It is what the customer linked, shown back so they can confirm it is the
        intake number rather than the firm's main line. It never comes from a
        client's message.
        """
        container.whatsapp = FakeProvider(name="meta")
        report = status_report.build(container)

        # Present as a field; its value comes from the provider's own session,
        # never from an inbound message.
        assert "connected_number" in report["whatsapp"] or report["whatsapp"]["provider"] == "meta"


class TestWorkflowCounts:
    def test_no_database_reports_nothing_rather_than_zeros(self, container):
        """Zeros would read as "nothing happened" rather than "nothing is
        recorded", which are different facts."""
        assert "workflows" not in status_report.build(container)

    def test_counts_are_included_when_there_is_a_database(self, container, monkeypatch):
        from app.runtime import workflow_stats as stats_module

        container.connect = object()
        monkeypatch.setattr(
            stats_module,
            "workflow_stats",
            lambda connect, days=7: stats_module.WorkflowStats(
                days=days, started=10, completed=8, failed=1, awaiting=1,
                median_seconds=42, retried=2,
            ),
        )

        workflows = status_report.build(container)["workflows"]

        assert workflows == {
            "completed": 8, "failed": 1, "awaiting": 1, "retried": 2, "median_seconds": 42,
        }

    def test_a_failing_query_does_not_break_the_report(self, container, monkeypatch):
        from app.runtime import workflow_stats as stats_module

        container.connect = object()
        monkeypatch.setattr(
            stats_module, "workflow_stats",
            lambda *a, **kw: (_ for _ in ()).throw(RuntimeError("no such table")),
        )

        assert "workflows" not in status_report.build(container)
        assert status_report.build(container)["version"]


class TestSending:
    def test_it_sends_to_the_cms(self, container):
        assert status_report.send(container) is True
        assert container.cms.reports[0]["version"]

    def test_a_failure_is_swallowed(self, container):
        """Telemetry. A CMS that is briefly unreachable must not take down the
        process that reads clients' documents — the next report is minutes away.
        """
        container.cms = FakeCms(fails=True)

        assert status_report.send(container) is False

    def test_a_broken_health_check_still_produces_a_report(self, container, monkeypatch):
        from app.runtime import health

        monkeypatch.setattr(
            health, "readiness", lambda *a, **kw: (_ for _ in ()).throw(RuntimeError("boom"))
        )

        report = status_report.build(container)

        assert report["components"] == {}
        assert report["version"]


class TestTheClientCall:
    def test_it_posts_to_the_status_endpoint(self, monkeypatch):
        from app.api.client import CmsClient
        from app.config.settings import Settings

        calls = []
        # Patched on the class: CmsClient uses slots, so an instance attribute
        # cannot be assigned — which is the point of slots.
        monkeypatch.setattr(
            CmsClient,
            "_request",
            lambda self, method, path, **kw: calls.append((method, path, kw)) or {},
        )

        CmsClient(Settings("https://cms.test", "key", "secret")).report_status({"version": "1.0.0"})

        assert calls[0][0] == "POST"
        assert calls[0][1] == "status"

    def test_it_is_blocked_when_the_cms_is_incompatible(self):
        """The gate covers every call out, including this one. A deployment that
        cannot work with its CMS should not be filling that CMS's operations
        screen with reassuring green."""
        from app.api.client import CmsClient, IncompatibleCms
        from app.api.compatibility import evaluate
        from app.config.settings import Settings

        client = CmsClient(Settings("https://cms.test", "key", "secret"))
        client._verdict = evaluate({"cms": {"api_contract": "2.0"}})  # noqa: SLF001

        with pytest.raises(IncompatibleCms):
            client.report_status({"version": "1.0.0"})
