import opentracing
from opentracing import Format

import ddtrace
from ddtrace.opentracer.span_context import SpanContext


class TestTracerCompatibility(object):
    """Ensure that our opentracer produces results in the underlying ddtracer."""

    def test_ottracer_uses_global_ddtracer(self):
        """Ensure that the opentracer will by default use the global ddtracer
        as its underlying Datadog tracer.
        """
        tracer = ddtrace.opentracer.Tracer()
        assert tracer._dd_tracer is ddtrace.tracer

    def test_custom_ddtracer(self):
        """A user should be able to specify their own Datadog tracer instance if
        they wish.
        """
        custom_dd_tracer = ddtrace.Tracer()
        tracer = ddtrace.opentracer.Tracer(dd_tracer=custom_dd_tracer)
        assert tracer._dd_tracer is custom_dd_tracer

    def test_ot_dd_global_tracers(self, global_tracer):
        """Ensure our test function opentracer_init() prep"""
        ot_tracer = global_tracer
        dd_tracer = global_tracer._dd_tracer

        # check all the global references
        assert ot_tracer is opentracing.tracer
        assert ot_tracer._dd_tracer is dd_tracer
        assert dd_tracer is ddtrace.tracer

    def test_ot_dd_nested_trace(self, ot_tracer, dd_tracer, test_spans):
        """Ensure intertwined usage of the opentracer and ddtracer."""

        with ot_tracer.start_span("my_ot_span") as ot_span:
            with dd_tracer.trace("my_dd_span") as dd_span:
                pass
        spans = test_spans.pop()
        assert len(spans) == 2

        # confirm the ordering
        assert spans[1] is ot_span._dd_span
        assert spans[0] is dd_span

        # check the parenting
        assert spans[0].parent_id is None
        assert spans[1].parent_id is None

    def test_dd_ot_nested_trace(self, ot_tracer, dd_tracer, test_spans):
        """Ensure intertwined usage of the opentracer and ddtracer."""
        with dd_tracer.trace("my_dd_span") as dd_span:
            with ot_tracer.start_span("my_ot_span") as ot_span:
                pass
        spans = test_spans.pop()
        assert len(spans) == 2

        # confirm the ordering
        assert spans[0] is dd_span
        assert spans[1] is ot_span._dd_span

        # check the parenting
        assert spans[0].parent_id is None
        assert spans[1].parent_id is spans[0].span_id

    def test_ot_dd_ot_dd_nested_trace(self, ot_tracer, dd_tracer, test_spans):
        """Ensure intertwined usage of the opentracer and ddtracer."""
        with ot_tracer.start_active_span("ot_span") as ot_scope:
            with dd_tracer.trace("dd_span") as dd_span:
                with ot_tracer.start_active_span("ot_span2") as ot_scope2:
                    with dd_tracer.trace("dd_span2") as dd_span2:
                        pass

        spans = test_spans.pop()
        assert len(spans) == 4

        spans = {span.name: span for span in spans}
        assert spans["ot_span"] == ot_scope.span._dd_span
        assert spans["dd_span"] == dd_span
        assert spans["ot_span2"] == ot_scope2.span._dd_span
        assert spans["dd_span2"] == dd_span2

        # check the parenting
        assert spans["ot_span"].parent_id is None
        assert spans["dd_span"].parent_id is spans["ot_span"].span_id
        assert spans["ot_span2"].parent_id is spans["dd_span"].span_id
        assert spans["dd_span2"].parent_id is spans["ot_span2"].span_id

    def test_ot_ot_dd_ot_dd_nested_trace_active(self, ot_tracer, dd_tracer, test_spans):
        """Ensure intertwined usage of the opentracer and ddtracer."""
        with ot_tracer.start_active_span("my_ot_span") as ot_scope:
            with ot_tracer.start_active_span("my_ot_span") as ot_scope2:
                with dd_tracer.trace("my_dd_span") as dd_span:
                    with ot_tracer.start_active_span("my_ot_span") as ot_scope3:
                        with dd_tracer.trace("my_dd_span") as dd_span2:
                            pass

        spans = test_spans.pop()
        assert len(spans) == 5

        # confirm the ordering
        assert spans[0] is ot_scope.span._dd_span
        assert spans[1] is ot_scope2.span._dd_span
        assert spans[2] is dd_span
        assert spans[3] is ot_scope3.span._dd_span
        assert spans[4] is dd_span2

        # check the parenting
        assert spans[0].parent_id is None
        assert spans[1].parent_id == spans[0].span_id
        assert spans[2].parent_id == spans[1].span_id
        assert spans[3].parent_id == spans[2].span_id
        assert spans[4].parent_id == spans[3].span_id

    def test_consecutive_trace(self, ot_tracer, dd_tracer, test_spans):
        """Ensure consecutive usage of the opentracer and ddtracer."""
        with ot_tracer.start_active_span("my_ot_span") as ot_scope:
            pass

        with dd_tracer.trace("my_dd_span") as dd_span:
            pass

        with ot_tracer.start_active_span("my_ot_span") as ot_scope2:
            pass

        with dd_tracer.trace("my_dd_span") as dd_span2:
            pass

        spans = test_spans.pop()
        assert len(spans) == 4

        # confirm the ordering
        assert spans[0] is ot_scope.span._dd_span
        assert spans[1] is dd_span
        assert spans[2] is ot_scope2.span._dd_span
        assert spans[3] is dd_span2

        # check the parenting
        assert spans[0].parent_id is None
        assert spans[1].parent_id is None
        assert spans[2].parent_id is None
        assert spans[3].parent_id is None

    def test_ddtrace_wrapped_fn(self, ot_tracer, dd_tracer, test_spans):
        """Ensure ddtrace wrapped functions work with the opentracer"""

        @dd_tracer.wrap()
        def fn():
            with ot_tracer.start_span("ot_span_inner"):
                pass

        with ot_tracer.start_active_span("ot_span_outer"):
            fn()

        spans = test_spans.pop()
        assert len(spans) == 3

        # confirm the ordering
        assert spans[0].name == "ot_span_outer"
        assert spans[1].name == "tests.opentracer.core.test_dd_compatibility.fn"
        assert spans[2].name == "ot_span_inner"

        # check the parenting
        assert spans[0].parent_id is None
        assert spans[1].parent_id is spans[0].span_id
        assert spans[2].parent_id is spans[1].span_id

    def test_distributed_trace_propagation(self, ot_tracer, dd_tracer, test_spans):
        """Ensure that a propagated span context is properly activated."""
        span_ctx = SpanContext(trace_id=123, span_id=456)
        carrier = {}
        ot_tracer.inject(span_ctx, Format.HTTP_HEADERS, carrier)

        # extract should activate the span so that a subsequent start_span
        # will inherit from the propagated span context
        ot_tracer.extract(Format.HTTP_HEADERS, carrier)

        with dd_tracer.trace("test") as span:
            pass

        assert span.parent_id == 456
        assert span.trace_id == 123

        spans = test_spans.pop()
        assert len(spans) == 1
