# Copyright (c) 2016 Uber Technologies, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

from __future__ import absolute_import

import time

import mock
import opentracing
import pytest
import tornado.web
import crossdock.server.api as api
from crossdock.server import server
from crossdock.server.api import Request, Downstream
from jaeger_client import Tracer, ConstSampler
from jaeger_client.reporter import InMemoryReporter
from tchannel import TChannel
from tornado.httpclient import HTTPRequest
from opentracing_instrumentation.request_context import TornadoScopeManager


def test_api_to_json():
    r = Request(serverRole='s2',
                downstream=Downstream(
                    serviceName='serviceName',
                    serverRole='s3',
                    encoding='json',
                    hostPort='localhost:123',
                    downstream=Downstream(
                        serviceName='serviceName',
                        serverRole='s4',
                        encoding='json',
                        hostPort='localhost:123',
                        downstream=None)))
    api.namedtuple_to_dict(r)


@pytest.fixture
def app():
    """Required by pytest-tornado's http_server fixture"""
    return tornado.web.Application()


# noinspection PyShadowingNames
@pytest.yield_fixture
def tracer():
    tracer = Tracer(
        service_name='test-tracer',
        sampler=ConstSampler(True),
        reporter=InMemoryReporter(),
        scope_manager=TornadoScopeManager()
    )
    opentracing.set_global_tracer(tracer)
    try:
        yield tracer
    finally:
        opentracing._reset_global_tracer()
        tracer.close()


PERMUTATIONS = []
for s2 in ['json', 'thrift']:
    for s3 in ['json', 'thrift']:
        for sampled in [True, False]:
            PERMUTATIONS.append((s2, s3, sampled))


@pytest.mark.parametrize('s2_encoding,s3_encoding,sampled', PERMUTATIONS)
@pytest.mark.gen_test
def test_trace_propagation(
        s2_encoding, s3_encoding, sampled,
        app,
        mock_server,
        tracer,
        http_server, base_url, http_client):

    # mock_server is created as a fixture, so we need to set tracer on it
    mock_server.tchannel._dep_tchannel._tracer = tracer
    mock_server.tchannel._dep_tchannel._trace = True

    server.register_http_handlers(app)
    server.register_tchannel_handlers(mock_server.tchannel)

    # verify that server is ready
    yield http_client.fetch(
        request=HTTPRequest(
            url=base_url,
            method='HEAD',
        )
    )

    tchannel = TChannel(name='test', tracer=None, trace=True)

    level3 = Downstream(
        serviceName='python',
        serverRole='s3',
        encoding=s3_encoding,
        hostPort=mock_server.hostport,
        downstream=None,
    )

    level2 = Downstream(
        serviceName='python',
        serverRole='s2',
        encoding=s2_encoding,
        hostPort=mock_server.hostport,
        downstream=level3,
    )

    with mock.patch('opentracing.tracer', tracer):
        assert opentracing.tracer == tracer  # sanity check that patch worked

        span = tracer.start_span('root')
        baggage = 'some baggage %d' % time.time()
        span.set_baggage_item(api.BAGGAGE_KEY, baggage)
        if not sampled:
            span.set_tag('sampling.priority', 0)
        with span:  # use span as context manager so that it's always finished
            with tchannel.context_provider.span_in_context(span):
                observed_span = server.observe_span()
                response_future = server.call_downstream(
                    tchannel=tchannel,
                    target=level2,
                )
            response = yield response_future
    assert response.span == observed_span
    assert response.downstream is not None
    assert response.downstream.span == observed_span
    assert response.downstream.downstream is None
