"""Tests for observability: metrics, tracing, and structured events."""

import logging

import pytest

from loom.observability.events import log_task_event, log_tool_call, log_orchestrator_tick
from loom.observability.metrics import Metrics, metrics_handler
from loom.observability.tracing import setup_tracing, trace_tool, _tracer


@pytest.fixture(autouse=True)
def reset_metrics():
    """Reset metrics before each test."""
    Metrics().reset()
    yield
    Metrics().reset()


async def test_metrics_increment_counter():
    m = Metrics()
    m.inc("tasks_created_total")
    m.inc("tasks_created_total")
    assert m.get_counter("tasks_created_total") == 2.0


async def test_metrics_increment_counter_with_labels():
    m = Metrics()
    m.inc("tasks_created_total", labels={"project": "proj-1"})
    m.inc("tasks_created_total", labels={"project": "proj-2"})
    m.inc("tasks_created_total", labels={"project": "proj-1"})
    assert m.get_counter("tasks_created_total", labels={"project": "proj-1"}) == 2.0
    assert m.get_counter("tasks_created_total", labels={"project": "proj-2"}) == 1.0


async def test_metrics_set_gauge():
    m = Metrics()
    m.set_gauge("claims_active", 5.0)
    assert m.get_gauge("claims_active") == 5.0
    m.set_gauge("claims_active", 3.0)
    assert m.get_gauge("claims_active") == 3.0


async def test_metrics_histogram_observe():
    m = Metrics()
    m.observe("db_query_duration_seconds", 0.05)
    m.observe("db_query_duration_seconds", 0.15)
    m.observe("db_query_duration_seconds", 1.5)
    obs = m.get_histogram("db_query_duration_seconds")
    assert len(obs) == 3
    assert 0.05 in obs


async def test_metrics_prometheus_format():
    m = Metrics()
    m.inc("test_counter", 42.0)
    m.set_gauge("test_gauge", 7.0)
    m.observe("test_hist", 0.5)
    output = metrics_handler()
    assert "test_counter 42.0" in output
    assert "test_gauge 7.0" in output
    assert "test_hist_count 1" in output
    assert 'test_hist_bucket{le="0.5"} 1' in output


async def test_trace_tool_noop_without_otel():
    """When OTEL is not configured, decorator should be transparent."""
    @trace_tool("test_tool")
    async def my_tool(x: int) -> int:
        return x * 2

    result = await my_tool(5)
    assert result == 10


async def test_trace_tool_creates_span(monkeypatch):
    """When tracer is set, span should be created."""
    spans = []

    class FakeSpan:
        def __init__(self, name):
            self.name = name
            self.attributes = {}
            spans.append(self)
        def set_attribute(self, key, value):
            self.attributes[key] = value
        def __enter__(self):
            return self
        def __exit__(self, *args):
            pass

    class FakeTracer:
        def start_as_current_span(self, name):
            return FakeSpan(name)

    import loom.observability.tracing as tracing_mod
    monkeypatch.setattr(tracing_mod, "_tracer", FakeTracer())

    @trace_tool("test_op")
    async def my_op(task_id: str = "") -> str:
        return "ok"

    result = await my_op(task_id="t-123")
    assert result == "ok"
    assert len(spans) == 1
    assert spans[0].attributes["tool.name"] == "test_op"
    assert spans[0].attributes["task.id"] == "t-123"


async def test_log_task_event_structure(caplog):
    with caplog.at_level(logging.INFO, logger="loom.events"):
        log_task_event("task_created", "t-001", "proj-1", extra_field="hello")
    assert len(caplog.records) == 1
    record = caplog.records[0]
    assert record.event_type == "task_created"
    assert record.task_id == "t-001"
    assert record.project_id == "proj-1"


async def test_log_tool_call_structure(caplog):
    with caplog.at_level(logging.INFO, logger="loom.events"):
        log_tool_call("loom_create", duration_ms=42.5, success=True)
    assert len(caplog.records) == 1
    record = caplog.records[0]
    assert record.tool_name == "loom_create"
    assert record.duration_ms == 42.5
    assert record.success is True


async def test_log_orchestrator_tick(caplog):
    with caplog.at_level(logging.INFO, logger="loom.events"):
        log_orchestrator_tick(swept=2, retried=1, escalated=0)
    assert len(caplog.records) == 1
    record = caplog.records[0]
    assert record.expired_swept == 2
    assert record.tasks_retried == 1
