from contextlib import contextmanager
from ploneintranet import api as pi_api
from ploneintranet.api.testing import IntegrationTestCase
from zope.globalrequest import getRequest
from zope.globalrequest import setRequest

import unittest

foo_disabled = pi_api.env.disabled("foo")


@foo_disabled
def test_module_foo():
    request = getRequest()

    # Strip from the request the non relevant data
    relevant_data = {
        key: value
        for key, value in request.items()
        if key
        not in [
            "ACTUAL_URL",
            "HTTP_X_THEME_ENABLED",
            "LANGUAGE",
            "LANGUAGE_TOOL",
            "PARENTS",
            "REQUEST_METHOD",
            "RESPONSE",
            "SERVER_NAME",
            "SERVER_PORT",
            "SERVER_URL",
            "URL",
            "__catalog_cache__",
            "method",
            "ploneintranet.layout.app.default",
        ]
    }
    return {
        "request": relevant_data,
        "feature_disabled": pi_api.env.is_disabled("foo"),
        "feature_enabled": pi_api.env.is_enabled("foo"),
    }


@contextmanager
def temporary_request(request):
    old_request = getRequest()
    setRequest(request)
    try:
        yield request
    finally:
        setRequest(old_request)


class TestEnv(unittest.TestCase):
    def assertEnabled(self, request):
        self.assertTrue(pi_api.env.is_enabled("foo", request))
        self.assertFalse(pi_api.env.is_disabled("foo", request))

    def assertDisabled(self, request):
        self.assertFalse(pi_api.env.is_enabled("foo", request))
        self.assertTrue(pi_api.env.is_disabled("foo", request))

    def test_indexing_disabled(self):
        """Test the context manager that disables the indexing queue"""
        # If the request has no 'disable-indexing' key
        # Check that it gets added and removed
        request = {}
        with pi_api.env.indexing_disabled(request):
            self.assertDictEqual(request, {"disable-indexing": True})
        self.assertDictEqual(request, {})

        # Otherwise check it is get modified to False and then restored
        for value in (False, None, True, 1):
            request["disable-indexing"] = value
            with pi_api.env.indexing_disabled(request):
                self.assertDictEqual(request, {"disable-indexing": True})
            self.assertDictEqual(request, {"disable-indexing": value})

        # It will also work if the request is completely borked
        request = object()
        with pi_api.env.indexing_disabled(request) as modified_request:
            self.assertIs(request, modified_request)

    def test_decorator_disabled_on_module_function(self):
        """Test the decorator that disables a flag on the request"""
        with temporary_request({}) as request:
            # The key related to the feature is not in the request
            with self.assertRaises(KeyError):
                request["foo"]

            # The feature is active
            self.assertEnabled(pi_api.env.is_enabled("foo", request))

            # When the function is called the key for the feature is added
            # to the request and the feature is evaluated as disabled
            self.assertDictEqual(
                test_module_foo(),
                {
                    "request": {"foo": foo_disabled.value},
                    "feature_disabled": True,
                    "feature_enabled": False,
                },
            )

            # When we are back in the test the key is removed from the request
            with self.assertRaises(KeyError):
                request["foo"]

            # The feature is still active
            self.assertEnabled(pi_api.env.is_enabled("foo", request))

    def test_decorator_context_manager(self):
        """Test the decorator that disables a flag on the request"""
        with temporary_request({}) as request:
            # The key related to the feature is not in the request
            with self.assertRaises(KeyError):
                request["foo"]

            self.assertTrue(pi_api.env.is_enabled("foo"))

            with pi_api.env.disabled("foo"):
                self.assertTrue(pi_api.env.is_disabled("foo"))
                self.assertDictEqual(request, {"foo": foo_disabled.value})

            self.assertTrue(pi_api.env.is_enabled("foo"))

            with self.assertRaises(KeyError):
                request["foo"]

    def test_value_restored(self):
        """Test that the value of the feature is restored after the context
        manager is exited"""
        with temporary_request({"foo": "bar"}) as request:
            self.assertEqual(request["foo"], "bar")
            self.assertEnabled(request)

            with pi_api.env.disabled("foo"):
                self.assertDisabled(request)
                self.assertEqual(request["foo"], foo_disabled.value)

            self.assertEqual(request["foo"], "bar")
            self.assertEnabled(request)

    def test_disabled_value_is_constant(self):
        """Test that the value that marks a feature as disabled is constant
        for all the instances of pi_api.env.disabled"""
        self.assertIs(foo_disabled.value, pi_api.env.disabled("bar").value)

    def test_nested_disabled(self):
        """Test that the context manager can be nested"""
        with temporary_request({}):
            self.assertTrue(pi_api.env.is_enabled("foo"))
            self.assertTrue(pi_api.env.is_enabled("bar"))

            with pi_api.env.disabled("foo"):
                self.assertFalse(pi_api.env.is_enabled("foo"))
                self.assertTrue(pi_api.env.is_enabled("bar"))

                with pi_api.env.disabled("bar"):
                    self.assertFalse(pi_api.env.is_enabled("foo"))
                    self.assertFalse(pi_api.env.is_enabled("bar"))

                self.assertFalse(pi_api.env.is_enabled("foo"))
                self.assertTrue(pi_api.env.is_enabled("bar"))

            self.assertTrue(pi_api.env.is_enabled("foo"))
            self.assertTrue(pi_api.env.is_enabled("bar"))

    def test_value_restored_nested(self):
        """Test that the value of the feature is restored after the context
        manager is exited"""
        with temporary_request({"foo": "foo", "bar": "bar"}) as request:
            with pi_api.env.disabled("foo") as foo:
                self.assertDictEqual(
                    request, {"foo": pi_api.env.disabled.value, "bar": "bar"}
                )
                with pi_api.env.disabled("bar") as bar:
                    self.assertEqual(foo._old_value, "foo")
                    self.assertEqual(bar._old_value, "bar")
                    self.assertDictEqual(
                        request,
                        {
                            "foo": pi_api.env.disabled.value,
                            "bar": pi_api.env.disabled.value,
                        },
                    )
                    pass

            self.assertDictEqual(request, {"foo": "foo", "bar": "bar"})


class TestDisabled(IntegrationTestCase):
    """Depends on getRequest()"""

    def assertEnabled(self, request=None):
        self.assertTrue(pi_api.env.is_enabled("foo", request))
        self.assertFalse(pi_api.env.is_disabled("foo", request))

    def assertDisabled(self, request=None):
        self.assertFalse(pi_api.env.is_enabled("foo", request))
        self.assertTrue(pi_api.env.is_disabled("foo", request))

    def test_decorator_disabled_on_module_function(self):
        """Test the decorator that disables a flag on the request"""
        self.assertEnabled()

        self.assertDictEqual(
            test_module_foo(),
            {
                "request": {"foo": foo_disabled.value},
                "feature_disabled": True,
                "feature_enabled": False,
            },
        )

        self.assertEnabled()

    def test_decorator_context_manager(self):
        """Test the decorator that disables a flag on the request"""

        self.assertEnabled()

        with pi_api.env.disabled("foo"):
            self.assertDisabled()

        self.assertEnabled()

    @foo_disabled
    def test_disabled_decorator_on_testmethod(self):
        self.assertDisabled()

    @unittest.expectedFailure
    @foo_disabled
    def test_disabled_decorator_on_testmethod_actually_is_tested(self):
        """Verify that the decorator does not accidentally disable
        the whole test method, instead of just disabling the feature."""
        self.assertTrue(False)


@unittest.skipUnless(
    pi_api.env.PY_311_OR_MORE, "TestCase.enterContext was added in Python 3.11"
)
class TestDisabledForTestCase(IntegrationTestCase):
    key = "ploneintranet.api.env.testing1"

    def setUp(self):
        super().setUp()
        self.disabled_cm = pi_api.env.disabled(self.key)
        self.enterContext(self.disabled_cm)

    def test_disabled_contextmanager_on_testcase(self):
        self.assertTrue(pi_api.env.is_disabled(self.key, self.request))
