import pytest
import asyncio
from proxym.observability import log_call, retry_with_log, get_log_buffer, _LOG_BUFFER
from proxym.observability.health_events import HealthEventStore, Severity


@pytest.fixture(autouse=True)
def clear_buffer():
    _LOG_BUFFER.clear()
    yield
    _LOG_BUFFER.clear()


class TestLogCallDecorator:
    def test_sync_function_logged(self):
        @log_call(level="info")
        def add(a, b): return a + b
        result = add(1, 2)
        assert result == 3
        logs = get_log_buffer()
        assert any(e["event"] == "call" for e in logs)
        assert any(e["event"] == "return" for e in logs)

    def test_error_logged(self):
        @log_call()
        def fail(): raise ValueError("boom")
        with pytest.raises(ValueError):
            fail()
        logs = get_log_buffer(level="error")
        assert len(logs) >= 1
        assert "boom" in logs[-1]["error"]

    def test_slow_warning(self):
        import time
        @log_call(slow_threshold_ms=10)
        def slow():
            time.sleep(0.02)
            return "done"
        slow()
        logs = get_log_buffer(level="warning")
        assert any(e.get("slow") for e in logs)

    def test_async_function(self):
        @log_call()
        async def fetch(): return 42
        result = asyncio.get_event_loop().run_until_complete(fetch())
        assert result == 42
        assert len(get_log_buffer()) >= 2

    def test_secrets_redacted(self):
        @log_call(include_args=True)
        def login(user, api_key): return True
        login("admin", api_key="sk-secret-123")
        logs = get_log_buffer()
        call_log = next(e for e in logs if e["event"] == "call")
        assert "sk-secret-123" not in str(call_log)
        assert "***" in str(call_log)


class TestRetryDecorator:
    def test_retries_on_error(self):
        attempts = []
        @retry_with_log(max_retries=3, backoff_base=0.01, retryable=(ValueError,))
        def flaky():
            attempts.append(1)
            if len(attempts) < 3:
                raise ValueError("not yet")
            return "ok"
        assert flaky() == "ok"
        assert len(attempts) == 3

    def test_exhausted_raises(self):
        @retry_with_log(max_retries=2, backoff_base=0.01, retryable=(ValueError,))
        def always_fail(): raise ValueError("nope")
        with pytest.raises(ValueError):
            always_fail()
        logs = get_log_buffer(level="error")
        assert any("exhausted" in e.get("event", "") for e in logs)


class TestHealthEventStore:
    def test_emit_and_get(self):
        store = HealthEventStore()
        store.emit(Severity.critical, "proxy", "Proxy down")
        active = store.get_active()
        assert len(active) == 1
        assert active[0].source == "proxy"

    def test_resolve(self):
        store = HealthEventStore()
        store.emit(Severity.critical, "proxy", "Proxy down")
        store.resolve("proxy")
        assert len(store.get_active()) == 0

    def test_summary(self):
        store = HealthEventStore()
        store.emit(Severity.critical, "proxy", "Down")
        store.emit(Severity.warning, "budget", "Low")
        s = store.get_summary()
        assert s["active_issues"] == 2
        assert s["critical"] == 1

    def test_history(self):
        store = HealthEventStore()
        store.emit(Severity.info, "proxy", "Started")
        store.emit(Severity.warning, "budget", "Low")
        store.emit(Severity.critical, "proxy", "Down")
        history = store.get_history(limit=10)
        assert len(history) == 3

    def test_history_filter_severity(self):
        store = HealthEventStore()
        store.emit(Severity.info, "proxy", "Started")
        store.emit(Severity.critical, "proxy", "Down")
        history = store.get_history(severity="critical")
        assert len(history) == 1
        assert history[0]["severity"] == "critical"
