import pytest

from ddtrace.opentracer.span import Span
from tests.utils import DummyTracer


@pytest.fixture
def nop_tracer():
    from ddtrace.opentracer import Tracer

    tracer = Tracer(service_name="mysvc", config={})
    # use the same test tracer used by the primary tests
    tracer._tracer = DummyTracer()
    return tracer


@pytest.fixture
def nop_span_ctx():
    from ddtrace.constants import AUTO_KEEP
    from ddtrace.opentracer.span_context import SpanContext

    return SpanContext(sampling_priority=AUTO_KEEP)


@pytest.fixture
def nop_span(nop_tracer, nop_span_ctx):
    return Span(nop_tracer, nop_span_ctx, "my_op_name")


class TestSpan(object):
    """Test the Datadog OpenTracing Span implementation."""

    def test_init(self, nop_tracer, nop_span_ctx):
        """Very basic test for skeleton code"""
        span = Span(nop_tracer, nop_span_ctx, "my_op_name")
        assert not span.finished

    def test_tags(self, nop_span):
        """Set a tag and get it back."""
        r = nop_span.set_tag("test", 23)
        assert nop_span._get_metric("test") == 23
        assert r is nop_span

    def test_set_baggage(self, nop_span):
        """Test setting baggage."""
        r = nop_span.set_baggage_item("test", 23)
        assert r is nop_span

        r = nop_span.set_baggage_item("1", 1).set_baggage_item("2", 2)
        assert r is nop_span

    def test_get_baggage(self, nop_span):
        """Test setting and getting baggage."""
        # test a single item
        nop_span.set_baggage_item("test", 23)
        assert int(nop_span.get_baggage_item("test")) == 23

        # test multiple items
        nop_span.set_baggage_item("1", "1").set_baggage_item("2", 2)
        assert int(nop_span.get_baggage_item("test")) == 23
        assert nop_span.get_baggage_item("1") == "1"
        assert int(nop_span.get_baggage_item("2")) == 2

    def test_log_kv(self, nop_span):
        """Ensure logging values doesn't break anything."""
        # just log a bunch of values
        nop_span.log_kv({"myval": 2})
        nop_span.log_kv({"myval2": 3})
        nop_span.log_kv({"myval3": 5})
        nop_span.log_kv({"myval": 2})

    def test_log_dd_kv(self, nop_span):
        """Ensure keys that can be handled by our impl. are indeed handled."""
        import traceback

        from ddtrace.constants import ERROR_MSG
        from ddtrace.constants import ERROR_STACK
        from ddtrace.constants import ERROR_TYPE

        stack_trace = str(traceback.format_stack())
        nop_span.log_kv(
            {
                "event": "error",
                "error": 3,
                "message": "my error message",
                "stack": stack_trace,
            }
        )

        # Ensure error flag is set...
        assert nop_span._dd_span.error
        # ...and that error tags are set with the correct key
        assert nop_span._get_tag(ERROR_STACK) == stack_trace
        assert nop_span._get_tag(ERROR_MSG) == "my error message"
        assert nop_span._get_metric(ERROR_TYPE) == 3

    def test_operation_name(self, nop_span):
        """Sanity check for setting the operation name."""
        # just try setting the operation name
        r = nop_span.set_operation_name("new_op_name")
        assert nop_span._dd_span.name == "new_op_name"
        assert r is nop_span

    def test_context_manager(self, nop_span):
        """Test the span context manager."""
        import time

        assert not nop_span.finished
        # run the context manager but since the span has not been added
        # to the span context, we will not get any traces
        with nop_span:
            time.sleep(0.005)

        # span should be finished when the context manager exits
        assert nop_span.finished

        # there should be no traces (see above comment)
        spans = nop_span.tracer._tracer.pop()
        assert len(spans) == 0

    def test_immutable_span_context(self, nop_span):
        """Ensure span contexts are immutable."""
        before_ctx = nop_span._context
        nop_span.set_baggage_item("key", "value")
        after_ctx = nop_span._context
        # should be different contexts
        assert before_ctx is not after_ctx


class TestSpanCompatibility(object):
    """Ensure our opentracer spans features correspond to datadog span features."""

    def test_set_tag(self, nop_span):
        nop_span.set_tag("test", 2)
        assert nop_span._get_metric("test") == 2

    def test_tag_resource_name(self, nop_span):
        nop_span.set_tag("resource.name", "myresource")
        assert nop_span._dd_span.resource == "myresource"

    def test_tag_span_type(self, nop_span):
        nop_span.set_tag("span.type", "db")
        assert nop_span._dd_span.span_type == "db"

    def test_tag_service_name(self, nop_span):
        nop_span.set_tag("service.name", "mysvc234")
        assert nop_span._dd_span.service == "mysvc234"

    def test_tag_db_statement(self, nop_span):
        nop_span.set_tag("db.statement", "SELECT * FROM USERS")
        assert nop_span._dd_span.resource == "SELECT * FROM USERS"

    def test_tag_peer_hostname(self, nop_span):
        nop_span.set_tag("peer.hostname", "peername")
        assert nop_span._dd_span.get_tag("out.host") == "peername"

    def test_tag_peer_port(self, nop_span):
        nop_span.set_tag("peer.port", 55555)
        assert nop_span._get_metric("network.destination.port") == 55555

    def test_tag_sampling_priority(self, nop_span):
        nop_span.set_tag("sampling.priority", "2")
        assert nop_span._dd_span.context.sampling_priority == "2"
