# Copyright (c) The OpenTracing Authors.
#
# 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

from unittest import mock

from opentracing.span import Span


class ScopeCompatibilityCheckMixin(object):
    """
    A mixin class for validation that a given scope manager implementation
    satisfies the requirements of the OpenTracing API.
    """

    def scope_manager(self):
        raise NotImplementedError('Subclass must implement scope_manager()')

    def run_test(self, test_fn):
        """
        Utility method that can be optionally defined by ScopeManager
        implementers to run the passed test_fn() function
        in a given environment, such as a coroutine or greenlet.
        By default, it simply runs the passed test_fn() function
        in the current thread.
        """

        test_fn()

    def test_missing_active_external(self):
        # test that 'active' does not fail outside the run_test()
        # implementation (greenlet or coroutine).
        scope_manager = self.scope_manager()
        assert scope_manager.active is None

    def test_missing_active(self):
        def fn():
            scope_manager = self.scope_manager()
            assert scope_manager.active is None

        self.run_test(fn)

    def test_activate(self):
        def fn():
            scope_manager = self.scope_manager()
            span = mock.MagicMock(spec=Span)

            scope = scope_manager.activate(span, False)
            assert scope is not None
            assert scope_manager.active is scope

            scope.close()
            assert span.finish.call_count == 0
            assert scope_manager.active is None

        self.run_test(fn)

    def test_activate_external(self):
        # test that activate() does not fail outside the run_test()
        # implementation (greenlet or corotuine).
        scope_manager = self.scope_manager()
        span = mock.MagicMock(spec=Span)

        scope = scope_manager.activate(span, False)
        assert scope is not None
        assert scope_manager.active is scope

        scope.close()
        assert span.finish.call_count == 0
        assert scope_manager.active is None

    def test_activate_finish_on_close(self):
        def fn():
            scope_manager = self.scope_manager()
            span = mock.MagicMock(spec=Span)

            scope = scope_manager.activate(span, True)
            assert scope is not None
            assert scope_manager.active is scope

            scope.close()
            assert span.finish.call_count == 1
            assert scope_manager.active is None

        self.run_test(fn)

    def test_activate_nested(self):
        def fn():
            # when a Scope is closed, the previous one must be re-activated.
            scope_manager = self.scope_manager()
            parent_span = mock.MagicMock(spec=Span)
            child_span = mock.MagicMock(spec=Span)

            with scope_manager.activate(parent_span, True) as parent:
                assert parent is not None
                assert scope_manager.active is parent

                with scope_manager.activate(child_span, True) as child:
                    assert child is not None
                    assert scope_manager.active is child

                assert scope_manager.active is parent

            assert parent_span.finish.call_count == 1
            assert child_span.finish.call_count == 1

            assert scope_manager.active is None

        self.run_test(fn)

    def test_activate_finish_on_close_nested(self):
        def fn():
            # finish_on_close must be correctly handled
            scope_manager = self.scope_manager()
            parent_span = mock.MagicMock(spec=Span)
            child_span = mock.MagicMock(spec=Span)

            parent = scope_manager.activate(parent_span, False)
            with scope_manager.activate(child_span, True):
                pass
            parent.close()

            assert parent_span.finish.call_count == 0
            assert child_span.finish.call_count == 1
            assert scope_manager.active is None

        self.run_test(fn)

    def test_close_wrong_order(self):
        def fn():
            # only the active `Scope` can be closed
            scope_manager = self.scope_manager()
            parent_span = mock.MagicMock(spec=Span)
            child_span = mock.MagicMock(spec=Span)

            parent = scope_manager.activate(parent_span, True)
            child = scope_manager.activate(child_span, True)
            parent.close()

            assert parent_span.finish.call_count == 0
            assert scope_manager.active == child

        self.run_test(fn)
