import contextlib
import logging
import typing as t

from ddtrace.testing.internal.utils import DDTESTOPT_ROOT_SPAN_RESOURCE
from ddtrace.testing.internal.utils import DDTraceTestContext
from ddtrace.testing.internal.utils import PlainTestContext
from ddtrace.testing.internal.utils import TestContext
from ddtrace.testing.internal.writer import TestOptWriter


log = logging.getLogger(__name__)


def install_global_trace_filter(writer: TestOptWriter) -> None:
    """
    Install a trace filter in the global ddtrace tracer that forwards spans generated by ddtrace to ddtrace.testing.
    """
    try:
        import ddtrace  # noqa: F401
    except ImportError:
        log.debug("ddrace is not available, not installing trace filter")
        return None

    from .span_processor import TestOptSpanProcessor

    span_processor = TestOptSpanProcessor(writer)

    ddtrace.tracer.configure(trace_processors=[span_processor])


def enable_all_ddtrace_integrations():
    try:
        from ddtrace._monkey import _patch_all

        _patch_all()
    except Exception:
        log.exception("Error enabling ddtrace integrations")


def uninstall_global_trace_filter() -> None:
    """
    Uninstall trace filters from the global ddtrace tracer.
    """
    try:
        import ddtrace
    except ImportError:
        return None

    ddtrace.tracer.configure(trace_processors=[])


def trace_context(ddtrace_enabled: bool) -> t.ContextManager[TestContext]:
    """
    Create a trace context for a test to run.

    If ddtrace is enabled, a ddtrace context will be started; any spans created inside the test (e.g., instrumented HTTP
    requests) will be children of this context. This context manager yields a `TestContext` object containing the
    trace_id and span_id of created context.

    If ddtrace is not enabled, yields a dummy context with a freshly generated trace_id and span_id.
    """
    if ddtrace_enabled:
        try:
            import ddtrace  # noqa: F401

            return _ddtrace_context()
        except ImportError:
            log.debug("ddrace is not available, falling back to non-ddtrace context")

    return _plain_context()


@contextlib.contextmanager
def _ddtrace_context() -> t.Generator[DDTraceTestContext, None, None]:
    import ddtrace

    # TODO: check if this breaks async tests.
    # This seems to be necessary because buggy ddtrace integrations can leave spans
    # unfinished, and spans for subsequent tests will have the wrong parent.
    ddtrace.tracer.context_provider.activate(None)

    with ddtrace.tracer.trace(DDTESTOPT_ROOT_SPAN_RESOURCE) as root_span:
        root_span.set_tag("type", "test")  # Selenium integration checks the span type.
        root_span.set_tag("span.kind", "test")
        yield DDTraceTestContext(root_span)


@contextlib.contextmanager
def _plain_context() -> t.Generator[PlainTestContext, None, None]:
    yield PlainTestContext()
