import warnings
from unittest.mock import patch
from django.test import override_settings

from cogwheels import exceptions
from cogwheels.tests.conf import settings
from cogwheels.tests.base import AppSettingTestCase
from cogwheels.tests.models import DefaultModel, ReplacementModel


class TestValidModelSettingOverride(AppSettingTestCase):
    """
    Tests the effect of overriding ``COGWHEELS_TESTS_VALID_MODEL``
    """
    def test_returns_default_model_by_default(self):
        self.assertIs(
            self.appsettingshelper.get_model('VALID_MODEL'), DefaultModel,
        )

    @patch('django.apps.apps.get_model')
    def test_returns_from_cache_after_first_import(self, mocked_method):
        settings.reset_caches()
        settings.get_model('VALID_MODEL')
        settings.get_model('VALID_MODEL')
        settings.get_model('VALID_MODEL')
        self.assertEqual(mocked_method.call_count, 1)

    @override_settings(COGWHEELS_TESTS_VALID_MODEL='tests.ReplacementModel')
    def test_successful_override(self):
        self.assertIs(
            self.appsettingshelper.get_model('VALID_MODEL'), ReplacementModel
        )

    @override_settings(COGWHEELS_TESTS_VALID_MODEL=1)
    def test_raises_correct_error_type_when_user_defined_value_is_not_a_string(self):
        with self.assertRaises(exceptions.OverrideValueTypeInvalid):
            self.appsettingshelper.get_model('VALID_MODEL')

    @override_settings(COGWHEELS_TESTS_VALID_MODEL='no_dots_here')
    def test_raises_correct_error_type_when_format_is_invalid(self):
        with self.assertRaises(exceptions.OverrideValueFormatInvalid):
            self.appsettingshelper.get_model('VALID_MODEL')

    @override_settings(COGWHEELS_TESTS_VALID_MODEL='tests.NonExistentModel')
    def test_raises_correct_error_type_when_model_not_importable(self):
        with self.assertRaises(exceptions.OverrideValueNotImportable):
            self.appsettingshelper.get_model('VALID_MODEL')


class TestInvalidDefaultModelSettings(AppSettingTestCase):
    """
    Tests what happens when an app setting (which is supposed to be a valid
    'model import string') is referenced, but the default value provided by the
    app developer is invalid.
    """
    def test_raises_correct_error_type_when_format_is_invalid(self):
        with self.assertRaises(exceptions.DefaultValueFormatInvalid):
            self.appsettingshelper.get_model('INCORRECT_FORMAT_MODEL')

    def test_raises_correct_error_type_when_model_not_importable(self):
        with self.assertRaises(exceptions.DefaultValueNotImportable):
            self.appsettingshelper.get_model('UNAVAILABLE_MODEL')


class TestReplacedModelSetting(AppSettingTestCase):

    @override_settings(COGWHEELS_TESTS_REPLACED_MODEL_SETTING='tests.ReplacementModel')
    def test_referencing_deprecated_setting_returns_correct_value_but_a_warning(self):
        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")
            self.assertIs(
                self.appsettingshelper.get_model('REPLACED_MODEL_SETTING'),
                ReplacementModel,
            )
            self.assertEqual(len(w), 1)
            self.assertIn(
                "The REPLACED_MODEL_SETTING app setting is deprecated in favour of using "
                "REPLACEMENT_MODEL_SETTING. Please update your code to reference the new setting, "
                "as continuing to reference REPLACED_MODEL_SETTING will cause an exception to be "
                "raised once support is removed in two versions time.",
                str(w[0])
            )

    def test_multiple_references_to_deprecated_setting_raises_a_warning_each_time(self):
        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")
            self.appsettingshelper.get_model('REPLACED_MODEL_SETTING')
            self.appsettingshelper.get_model('REPLACED_MODEL_SETTING')
            self.appsettingshelper.get_model('REPLACED_MODEL_SETTING')
            self.assertEqual(len(w), 3)

    def test_no_warnings_raised_by_replacement_setting_by_default(self):
        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")
            self.assertIs(
                self.appsettingshelper.get_model('REPLACEMENT_MODEL_SETTING'),
                DefaultModel,
            )
            self.assertEqual(len(w), 0)

    @override_settings(COGWHEELS_TESTS_REPLACED_MODEL_SETTING='tests.ReplacementModel')
    def test_single_warning_raised_by_replacement_setting_when_deprecated_setting_value_used(self):
        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")
            self.assertIs(
                self.appsettingshelper.get_model('REPLACEMENT_MODEL_SETTING'),
                ReplacementModel,
            )
            self.assertEqual(len(w), 1)
            self.assertIn(
                "The COGWHEELS_TESTS_REPLACED_MODEL_SETTING setting is deprecated in favour of "
                "using COGWHEELS_TESTS_REPLACEMENT_MODEL_SETTING. Please update your Django "
                "settings to use the new setting, otherwise the app will revert to it's default "
                "behaviour once support for COGWHEELS_TESTS_REPLACED_MODEL_SETTING is removed in "
                "two versions time.",
                str(w[0])
            )

    @override_settings(COGWHEELS_TESTS_REPLACED_MODEL_SETTING='tests.ReplacementModel')
    def test_using_suppress_warnings_has_the_desired_effect(self):
        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")
            self.appsettingshelper.get_model('REPLACED_MODEL_SETTING', suppress_warnings=True)
            self.appsettingshelper.get_model('REPLACEMENT_MODEL_SETTING', suppress_warnings=True)
            self.assertEqual(len(w), 0)

    def test_warning_not_raised_if_not_overridden_and_warn_only_if_overridden_is_true(self):
        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")
            self.appsettingshelper.get_model('REPLACED_MODEL_SETTING', warn_only_if_overridden=True)
        self.assertEqual(len(w), 0)

    @override_settings(COGWHEELS_TESTS_REPLACED_MODEL_SETTING='tests.ReplacementModel')
    def test_warning_is_raised_if_overridden_and_warn_only_if_overrridden_is_true(self):
        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")
            self.appsettingshelper.get_model('REPLACED_MODEL_SETTING', warn_only_if_overridden=True)
        self.assertEqual(len(w), 1)
