import sys
from datetime import datetime, timedelta
from types import ModuleType

from django.conf import (
    PASSWORD_RESET_TIMEOUT_DAYS_DEPRECATED_MSG, Settings, settings,
)
from django.contrib.auth.models import User
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase, ignore_warnings
from django.utils.deprecation import RemovedInDjango40Warning


class DeprecationTests(TestCase):
    msg = PASSWORD_RESET_TIMEOUT_DAYS_DEPRECATED_MSG

    @ignore_warnings(category=RemovedInDjango40Warning)
    def test_timeout(self):
        """The token is valid after n days, but no greater."""
        # Uses a mocked version of PasswordResetTokenGenerator so we can change
        # the value of 'now'.
        class Mocked(PasswordResetTokenGenerator):
            def __init__(self, now):
                self._now_val = now

            def _now(self):
                return self._now_val

        user = User.objects.create_user('tokentestuser', 'test2@example.com', 'testpw')
        p0 = PasswordResetTokenGenerator()
        tk1 = p0.make_token(user)
        p1 = Mocked(datetime.now() + timedelta(settings.PASSWORD_RESET_TIMEOUT_DAYS))
        self.assertTrue(p1.check_token(user, tk1))
        p2 = Mocked(datetime.now() + timedelta(settings.PASSWORD_RESET_TIMEOUT_DAYS + 1))
        self.assertFalse(p2.check_token(user, tk1))
        with self.settings(PASSWORD_RESET_TIMEOUT_DAYS=1):
            self.assertEqual(settings.PASSWORD_RESET_TIMEOUT, 60 * 60 * 24)
            p3 = Mocked(datetime.now() + timedelta(settings.PASSWORD_RESET_TIMEOUT_DAYS))
            self.assertTrue(p3.check_token(user, tk1))
            p4 = Mocked(datetime.now() + timedelta(settings.PASSWORD_RESET_TIMEOUT_DAYS + 1))
            self.assertFalse(p4.check_token(user, tk1))

    def test_override_settings_warning(self):
        with self.assertRaisesMessage(RemovedInDjango40Warning, self.msg):
            with self.settings(PASSWORD_RESET_TIMEOUT_DAYS=2):
                pass

    def test_settings_init_warning(self):
        settings_module = ModuleType('fake_settings_module')
        settings_module.SECRET_KEY = 'foo'
        settings_module.PASSWORD_RESET_TIMEOUT_DAYS = 2
        sys.modules['fake_settings_module'] = settings_module
        try:
            with self.assertRaisesMessage(RemovedInDjango40Warning, self.msg):
                Settings('fake_settings_module')
        finally:
            del sys.modules['fake_settings_module']

    def test_access_warning(self):
        with self.assertRaisesMessage(RemovedInDjango40Warning, self.msg):
            settings.PASSWORD_RESET_TIMEOUT_DAYS
        # Works a second time.
        with self.assertRaisesMessage(RemovedInDjango40Warning, self.msg):
            settings.PASSWORD_RESET_TIMEOUT_DAYS

    @ignore_warnings(category=RemovedInDjango40Warning)
    def test_access(self):
        with self.settings(PASSWORD_RESET_TIMEOUT_DAYS=2):
            self.assertEqual(settings.PASSWORD_RESET_TIMEOUT_DAYS, 2)
            # Works a second time.
            self.assertEqual(settings.PASSWORD_RESET_TIMEOUT_DAYS, 2)

    def test_use_both_settings_init_error(self):
        msg = (
            'PASSWORD_RESET_TIMEOUT_DAYS/PASSWORD_RESET_TIMEOUT are '
            'mutually exclusive.'
        )
        settings_module = ModuleType('fake_settings_module')
        settings_module.SECRET_KEY = 'foo'
        settings_module.PASSWORD_RESET_TIMEOUT_DAYS = 2
        settings_module.PASSWORD_RESET_TIMEOUT = 2000
        sys.modules['fake_settings_module'] = settings_module
        try:
            with self.assertRaisesMessage(ImproperlyConfigured, msg):
                Settings('fake_settings_module')
        finally:
            del sys.modules['fake_settings_module']
