"""trace scope — 중첩·복원·꺼짐·실패 삼킴."""
import pytest

from app.modules.llm.opik_trace import current_trace, open_trace


@pytest.fixture(autouse=True)
def _v2_on(monkeypatch):
    from app.core import config
    monkeypatch.setattr(config.settings, "opik_trace_v2_enabled", True)


@pytest.fixture(autouse=True)
def _no_real_client(monkeypatch):
    """진짜 Opik 클라이언트를 만들지 않는다 — 시험은 망을 안 탄다.

    ★구현과 같은 함수로 fake 를 만들지 않는다. 여기서는 payload 를
    받아 적기만 하는 아주 단순한 대역을 쓴다.
    """
    sent = []

    class _FakeTrace:
        def __init__(self, **kw):
            self.kw = kw
            sent.append(("trace", kw))

        def end(self, **kw):
            sent.append(("end", kw))

        def update(self, **kw):
            sent.append(("update", kw))

    class _FakeClient:
        def trace(self, **kw):
            return _FakeTrace(**kw)

    from app.modules.llm import opik_trace
    monkeypatch.setattr(opik_trace, "_get_client", lambda: _FakeClient())
    return sent


def test_no_trace_outside_scope():
    assert current_trace() is None


def test_scope_sets_and_restores():
    with open_trace(name="step:a", tags=["step:a"], metadata={},
                    thread_id="t1") as h:
        assert h is not None
        assert current_trace() is h
        assert current_trace().name == "step:a"
    assert current_trace() is None


def test_inner_scope_wins_and_outer_returns():
    with open_trace(name="step:a", tags=[], metadata={}, thread_id="t1") as a:
        with open_trace(name="still:S1sh1", tags=[], metadata={},
                        thread_id="t1") as b:
            assert current_trace() is b
            assert b.uid != a.uid
        assert current_trace() is a


def test_scope_restores_on_exception():
    with pytest.raises(ValueError):
        with open_trace(name="step:a", tags=[], metadata={}, thread_id=None):
            raise ValueError("본 작업이 터졌다")
    assert current_trace() is None


def test_disabled_yields_none_and_sends_nothing(monkeypatch, _no_real_client):
    from app.core import config
    monkeypatch.setattr(config.settings, "opik_trace_v2_enabled", False)
    with open_trace(name="step:a", tags=[], metadata={}, thread_id="t") as h:
        assert h is None
        assert current_trace() is None
    assert _no_real_client == []


def test_client_failure_is_non_fatal(monkeypatch):
    """기록이 터져도 본 작업은 산다."""
    from app.modules.llm import opik_trace

    def _boom():
        raise RuntimeError("Opik 서버가 죽었다")

    monkeypatch.setattr(opik_trace, "_get_client", _boom)
    with open_trace(name="step:a", tags=[], metadata={}, thread_id="t") as h:
        assert h is None          # 부모가 없을 뿐
    assert current_trace() is None


def test_bind_and_reset_for_worker_threads():
    from app.modules.llm.opik_trace import bind_trace, reset_trace
    with open_trace(name="step:a", tags=[], metadata={},
                    thread_id="t") as parent:
        token = bind_trace(parent)
        try:
            assert current_trace() is parent
        finally:
            reset_trace(token)
