"""
Tests for Phase 55: Service Discovery and Inter-Module Communication.
"""

import json
import time

import pytest


@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
    """Redirect all service mesh storage to tmp_path and reset in-memory state."""
    services_dir = tmp_path / "services"
    services_dir.mkdir(parents=True, exist_ok=True)

    import brynq.server as srv
    monkeypatch.setattr(srv, "BROKER_DIR", tmp_path)

    import brynq.service_mesh as sm
    monkeypatch.setattr(sm, "SERVICES_DIR", services_dir)
    monkeypatch.setattr(sm, "CALLS_LOG", services_dir / "calls.jsonl")
    sm.reset_mesh()


# ── Register Service ─────────────────────────────────────────────────────

class TestRegisterService:
    def test_register_basic(self):
        from brynq.service_mesh import register_service
        svc = register_service("auth", "1.0.0", {"login": lambda p: "ok"})
        assert svc["name"] == "auth"
        assert svc["version"] == "1.0.0"
        assert "login" in svc["endpoint_names"]
        assert svc["status"] == "active"

    def test_register_with_capabilities(self):
        from brynq.service_mesh import register_service
        svc = register_service("auth", "1.0.0", {"login": lambda p: "ok"}, capabilities=["authentication", "oauth"])
        assert "authentication" in svc["capabilities"]
        assert "oauth" in svc["capabilities"]

    def test_register_with_health_check(self):
        from brynq.service_mesh import register_service, get_service_health
        register_service("db", "1.0.0", {"query": lambda p: []}, health_check_fn=lambda: True)
        health = get_service_health("db")
        assert health["db"]["status"] == "healthy"

    def test_register_multiple_endpoints(self):
        from brynq.service_mesh import register_service
        endpoints = {
            "get": lambda p: "data",
            "set": lambda p: "ok",
            "delete": lambda p: "deleted",
        }
        svc = register_service("cache", "2.0.0", endpoints)
        assert len(svc["endpoint_names"]) == 3

    def test_register_initializes_stats(self):
        from brynq.service_mesh import register_service
        svc = register_service("s", "1.0.0", {"ping": lambda: "pong"})
        assert svc["call_count"] == 0
        assert svc["error_count"] == 0


# ── Discover ─────────────────────────────────────────────────────────────

class TestDiscover:
    def test_discover_by_name(self):
        from brynq.service_mesh import register_service, discover
        register_service("auth", "1.0.0", {"login": lambda p: "ok"})
        results = discover(name="auth")
        assert len(results) == 1
        assert results[0]["name"] == "auth"

    def test_discover_nonexistent(self):
        from brynq.service_mesh import discover
        results = discover(name="ghost")
        assert results == []

    def test_discover_by_capability(self):
        from brynq.service_mesh import register_service, discover
        register_service("auth", "1.0.0", {"login": lambda p: "ok"}, capabilities=["auth"])
        register_service("cache", "1.0.0", {"get": lambda p: None}, capabilities=["storage"])
        results = discover(capability="auth")
        assert len(results) == 1
        assert results[0]["name"] == "auth"

    def test_discover_all(self):
        from brynq.service_mesh import register_service, discover
        register_service("a", "1.0.0", {"x": lambda: 1})
        register_service("b", "1.0.0", {"y": lambda: 2})
        results = discover()
        assert len(results) == 2

    def test_discover_excludes_deregistered(self):
        from brynq.service_mesh import register_service, deregister, discover
        register_service("old", "1.0.0", {"x": lambda: 1})
        deregister("old")
        results = discover()
        assert len(results) == 0


# ── Call Service ─────────────────────────────────────────────────────────

class TestCallService:
    def test_call_basic(self):
        from brynq.service_mesh import register_service, call_service
        register_service("math", "1.0.0", {"add": lambda p: p["a"] + p["b"]})
        result = call_service("math", "add", {"a": 2, "b": 3})
        assert result["success"] is True
        assert result["result"] == 5

    def test_call_no_payload(self):
        from brynq.service_mesh import register_service, call_service
        register_service("ping", "1.0.0", {"ping": lambda: "pong"})
        result = call_service("ping", "ping")
        assert result["success"] is True
        assert result["result"] == "pong"

    def test_call_nonexistent_service(self):
        from brynq.service_mesh import call_service
        with pytest.raises(ValueError, match="not found"):
            call_service("ghost", "method")

    def test_call_nonexistent_method(self):
        from brynq.service_mesh import register_service, call_service
        register_service("s", "1.0.0", {"a": lambda: 1})
        with pytest.raises(ValueError, match="not found"):
            call_service("s", "b")

    def test_call_handler_error_captured(self):
        from brynq.service_mesh import register_service, call_service
        def bad_handler(p):
            raise RuntimeError("boom")
        register_service("s", "1.0.0", {"fail": bad_handler})
        result = call_service("s", "fail", {})
        assert result["success"] is False
        assert "boom" in result["error"]

    def test_call_increments_stats(self):
        from brynq.service_mesh import register_service, call_service, get_service_stats
        register_service("s", "1.0.0", {"x": lambda: 1})
        call_service("s", "x")
        call_service("s", "x")
        stats = get_service_stats("s")
        assert stats["s"]["calls"] == 2

    def test_call_tracks_latency(self):
        from brynq.service_mesh import register_service, call_service
        register_service("s", "1.0.0", {"x": lambda: 1})
        result = call_service("s", "x")
        assert "latency_ms" in result
        assert result["latency_ms"] >= 0


# ── Health ───────────────────────────────────────────────────────────────

class TestHealth:
    def test_health_healthy(self):
        from brynq.service_mesh import register_service, get_service_health
        register_service("s", "1.0.0", {"x": lambda: 1}, health_check_fn=lambda: True)
        health = get_service_health("s")
        assert health["s"]["status"] == "healthy"

    def test_health_unhealthy(self):
        from brynq.service_mesh import register_service, get_service_health
        register_service("s", "1.0.0", {"x": lambda: 1}, health_check_fn=lambda: False)
        health = get_service_health("s")
        assert health["s"]["status"] == "unhealthy"

    def test_health_check_exception_is_unhealthy(self):
        from brynq.service_mesh import register_service, get_service_health
        def explode():
            raise RuntimeError("broken")
        register_service("s", "1.0.0", {"x": lambda: 1}, health_check_fn=explode)
        health = get_service_health("s")
        assert health["s"]["status"] == "unhealthy"

    def test_health_not_found(self):
        from brynq.service_mesh import get_service_health
        health = get_service_health("ghost")
        assert health["ghost"]["status"] == "not_found"

    def test_health_all_services(self):
        from brynq.service_mesh import register_service, get_service_health
        register_service("a", "1.0.0", {"x": lambda: 1})
        register_service("b", "1.0.0", {"y": lambda: 2})
        health = get_service_health()
        assert "a" in health
        assert "b" in health


# ── Middleware ────────────────────────────────────────────────────────────

class TestMiddleware:
    def test_middleware_runs_on_call(self):
        from brynq.service_mesh import register_service, register_middleware, call_service
        log = []
        register_middleware("logger", lambda phase, ctx: log.append((phase, ctx["service"])))
        register_service("s", "1.0.0", {"x": lambda: 1})
        call_service("s", "x")
        phases = [entry[0] for entry in log]
        assert "before" in phases
        assert "after" in phases

    def test_middleware_error_does_not_break_call(self):
        from brynq.service_mesh import register_service, register_middleware, call_service
        def bad_mw(phase, ctx):
            raise RuntimeError("mw crash")
        register_middleware("bad", bad_mw)
        register_service("s", "1.0.0", {"x": lambda: 42})
        result = call_service("s", "x")
        assert result["success"] is True
        assert result["result"] == 42


# ── Stats ────────────────────────────────────────────────────────────────

class TestStats:
    def test_stats_empty_service(self):
        from brynq.service_mesh import register_service, get_service_stats
        register_service("s", "1.0.0", {"x": lambda: 1})
        stats = get_service_stats("s")
        assert stats["s"]["calls"] == 0
        assert stats["s"]["errors"] == 0

    def test_stats_after_calls(self):
        from brynq.service_mesh import register_service, call_service, get_service_stats
        register_service("s", "1.0.0", {"x": lambda: 1})
        call_service("s", "x")
        call_service("s", "x")
        stats = get_service_stats("s")
        assert stats["s"]["calls"] == 2
        assert stats["s"]["avg_latency_ms"] >= 0

    def test_stats_error_rate(self):
        from brynq.service_mesh import register_service, call_service, get_service_stats
        def maybe_fail(p):
            if p and p.get("fail"):
                raise RuntimeError("fail")
            return "ok"
        register_service("s", "1.0.0", {"x": maybe_fail})
        call_service("s", "x", {"fail": False})
        call_service("s", "x", {"fail": True})
        stats = get_service_stats("s")
        assert stats["s"]["errors"] == 1
        assert stats["s"]["error_rate"] == 50.0

    def test_stats_all_services(self):
        from brynq.service_mesh import register_service, get_service_stats
        register_service("a", "1.0.0", {"x": lambda: 1})
        register_service("b", "1.0.0", {"y": lambda: 2})
        stats = get_service_stats()
        assert "a" in stats
        assert "b" in stats

    def test_stats_nonexistent(self):
        from brynq.service_mesh import get_service_stats
        stats = get_service_stats("ghost")
        assert stats == {}


# ── Deregister ───────────────────────────────────────────────────────────

class TestDeregister:
    def test_deregister_existing(self):
        from brynq.service_mesh import register_service, deregister, discover
        register_service("s", "1.0.0", {"x": lambda: 1})
        assert deregister("s") is True
        assert discover(name="s") == []  # discover filters inactive

    def test_deregister_nonexistent(self):
        from brynq.service_mesh import deregister
        assert deregister("ghost") is False

    def test_deregister_prevents_calls(self):
        from brynq.service_mesh import register_service, deregister, call_service
        register_service("s", "1.0.0", {"x": lambda: 1})
        deregister("s")
        # Endpoint handlers are removed, so calling should fail
        with pytest.raises(ValueError):
            call_service("s", "x")
