import logging
from io import StringIO
from types import SimpleNamespace

import pytest

from terminaluse.lib.sandbox import entrypoint
from terminaluse.lib.sandbox.entrypoint import _log_timing_or_invocation


@pytest.mark.unit
def test_timing_logs_default_to_debug(monkeypatch, caplog):
    monkeypatch.delenv("TERMINALUSE_SANDBOX_TIMING_LOGS", raising=False)
    logger = logging.getLogger("test.entrypoint.default")

    with caplog.at_level(logging.DEBUG):
        _log_timing_or_invocation(logger, "[TIMING] payload_read: 10ms")

    assert any(
        record.levelno == logging.DEBUG and "[TIMING] payload_read: 10ms" in record.message for record in caplog.records
    )


@pytest.mark.unit
def test_timing_logs_can_be_promoted_to_info(monkeypatch, caplog):
    monkeypatch.setenv("TERMINALUSE_SANDBOX_TIMING_LOGS", "true")
    logger = logging.getLogger("test.entrypoint.info")

    with caplog.at_level(logging.INFO):
        _log_timing_or_invocation(logger, "[TIMING] payload_read: 10ms")

    assert any(
        record.levelno == logging.INFO and "[TIMING] payload_read: 10ms" in record.message for record in caplog.records
    )


class _FakeSpanContextManager:
    def __init__(self, name: str, calls: list[tuple[str, str]]):
        self._name = name
        self._calls = calls

    def __enter__(self):
        self._calls.append(("enter", self._name))
        return SimpleNamespace(set_attribute=lambda *args, **kwargs: None)

    def __exit__(self, exc_type, exc, tb):
        self._calls.append(("exit", self._name))
        return False


class _FakeTracer:
    def __init__(self, calls: list[tuple[str, str]]):
        self._calls = calls

    def start_as_current_span(self, name: str, **kwargs):  # noqa: ARG002
        return _FakeSpanContextManager(name, self._calls)


@pytest.mark.unit
def test_main_attaches_trace_context_before_extract_trace_context_span(monkeypatch):
    calls: list[tuple[str, str]] = []

    payload = (
        '{"method":"task/create","handler":{"module":"demo","function":"on_create"},'
        '"params":{"request":{"id":"req-1"}},"trace_context":{"traceparent":"00-abc-abc-01"}}'
    )
    monkeypatch.setattr(entrypoint.sys, "stdin", StringIO(payload))
    monkeypatch.setattr(entrypoint.sys, "stdout", StringIO())
    monkeypatch.setattr(entrypoint, "_configure_logging", lambda: None)
    monkeypatch.setattr(entrypoint, "_log_timing_or_invocation", lambda *args, **kwargs: None)
    monkeypatch.setattr(entrypoint, "_load_params", lambda method, data: SimpleNamespace())
    monkeypatch.setattr(entrypoint, "_load_handler", lambda handler_ref: lambda *args, **kwargs: None)
    monkeypatch.setattr(entrypoint, "_invoke_handler", lambda method, params, fn: None)
    monkeypatch.setattr(entrypoint, "extract", lambda carrier: "ctx")
    monkeypatch.setattr(entrypoint, "tracer", _FakeTracer(calls))
    monkeypatch.setattr(entrypoint.otel_context, "attach", lambda ctx: calls.append(("attach", str(ctx))) or "token")
    monkeypatch.setattr(entrypoint.otel_context, "detach", lambda token: calls.append(("detach", str(token))))

    result = entrypoint.main()

    assert result == 0
    assert ("attach", "ctx") in calls
    assert ("enter", "sandbox.entrypoint.extract_trace_context") in calls
    assert calls.index(("attach", "ctx")) < calls.index(("enter", "sandbox.entrypoint.extract_trace_context"))
