from datetime import datetime, timedelta
from decimal import Decimal
from django.test import TestCase, RequestFactory
from django.contrib.auth import get_user_model
from django.contrib import admin
from django.db import models
from django.core.exceptions import ValidationError
from django.core.files.uploadedfile import SimpleUploadedFile
from django.utils.translation import gettext_lazy as _
from unittest.mock import Mock, patch
from rules.contrib.models import RulesModelMixin, RulesModelBase
from contrib.models import CommonModel
from contrib.rules import has_global_perm
from contrib.admin import CommonAdminMixin
from utils import file_size_validator, RestrictedFileField, cvt_to_decimal, get_member, normalize_name, normalize_filename, coming_midnight, mondays_until_nth

User = get_user_model()

class CommonModelTestCase(TestCase):
    def test_common_model_abstract_base(self):
        """Test that CommonModel provides the correct meta attributes"""
        meta = CommonModel._meta
        self.assertTrue(meta.abstract)
        expected_permissions = (
            'add_global', 'change_global', 'view_global', 'delete_global', 'list_global', 'view',
        )
        self.assertEqual(meta.default_permissions, expected_permissions)

    def test_common_model_inheritance(self):
        """Test that CommonModel has rules mixin functionality"""
        # Test that CommonModel has the expected functionality
        # Since it's abstract, we can't instantiate it directly
        # but we can check its metaclass and mixins
        self.assertTrue(issubclass(CommonModel, RulesModelMixin))
        self.assertEqual(CommonModel.__class__, RulesModelBase)


class GlobalPermissionRulesTestCase(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )

    def test_has_global_perm_predicate_creation(self):
        """Test that has_global_perm creates a predicate function"""
        # has_global_perm is a decorator factory, not a direct predicate
        predicate = has_global_perm('auth.add_user')
        self.assertTrue(callable(predicate))

    def test_has_global_perm_with_superuser(self):
        """Test that superusers have global permissions"""
        self.user.is_superuser = True
        self.user.save()

        predicate = has_global_perm('auth.add_user')
        result = predicate(self.user, None)
        self.assertTrue(result)

    def test_has_global_perm_with_regular_user(self):
        """Test that regular users don't automatically have global permissions"""
        predicate = has_global_perm('auth.add_user')
        result = predicate(self.user, None)
        self.assertFalse(result)


class CommonAdminMixinTestCase(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(username='testuser', password='testpass')

    def test_formfield_for_dbfield_with_formfield_overrides(self):
        """Test formfield_for_dbfield when db_field class is in formfield_overrides"""
        # Create a test admin instance that inherits from Django's ModelAdmin
        class TestAdmin(CommonAdminMixin, admin.ModelAdmin):
            formfield_overrides = {
                models.ForeignKey: {'widget': Mock()}
            }

        # Create a mock model to use with the admin
        class TestModel:
            _meta = Mock()
            _meta.app_label = 'test'

        admin_instance = TestAdmin(TestModel, admin.site)

        # Create a mock ForeignKey field to trigger the missing line 147
        db_field = models.ForeignKey(User, on_delete=models.CASCADE)

        # Create a test request
        request = RequestFactory().get('/')
        request.user = self.user

        # Call the method to test formfield_overrides usage
        result = admin_instance.formfield_for_dbfield(db_field, request, help_text='Test help text')

        # Verify that the formfield_overrides were used
        self.assertIsNotNone(result)


class UtilsTestCase(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )

    def test_file_size_validator_exceeds_limit(self):
        """Test file_size_validator when file exceeds size limit"""
        validator = file_size_validator(1)  # 1MB limit

        # Create a mock file that exceeds the limit (2MB)
        mock_file = Mock()
        mock_file.size = 2 * 1024 * 1024  # 2MB

        with self.assertRaises(ValidationError) as cm:
            validator(mock_file)

        # Check for the translated error message
        expected_message = str(_('Please keep filesize under {} MiB. Current filesize: {:10.2f} MiB.').format(1, 2.00))
        self.assertIn(expected_message, str(cm.exception))

    def test_restricted_file_field_content_type_not_supported(self):
        """Test RestrictedFileField when content type is not supported"""
        field = RestrictedFileField(content_types=['image/jpeg'])

        # Create mock data with unsupported content type
        mock_data = Mock()
        mock_data.file = Mock()
        mock_data.file.content_type = "text/plain"

        # Mock the super().clean() to return our mock data
        with patch.object(models.FileField, 'clean', return_value=mock_data):
            with self.assertRaises(ValidationError) as cm:
                field.clean("dummy")

            # Check for the translated error message
            expected_message = str(_('Filetype not supported.'))
            self.assertIn(expected_message, str(cm.exception))

    def test_restricted_file_field_size_exceeds_limit(self):
        """Test RestrictedFileField when file size exceeds limit"""
        field = RestrictedFileField(max_upload_size=1)  # 1 byte limit

        # Create mock data with file that exceeds size limit
        mock_data = Mock()
        mock_data.file = Mock()
        mock_data.file.content_type = "text/plain"
        mock_data.file._size = 2  # 2 bytes, exceeds limit

        # Mock the super().clean() to return our mock data
        with patch.object(models.FileField, 'clean', return_value=mock_data):
            with self.assertRaises(ValidationError) as cm:
                field.clean("dummy")

            # Check for the translated error message
            expected_message = str(_('Please keep filesize under {}. Current filesize: {}').format(1, 2))
            self.assertIn(expected_message, str(cm.exception))

    def test_mondays_until_nth(self):
        """Test mondays_until_nth function"""
        # Test with n=2 to get 3 Mondays (including the 0th)
        result = mondays_until_nth(2)

        # Should return a list of 3 dates
        self.assertEqual(len(result), 3)

        # All dates should be Mondays (weekday 0)
        for date in result:
            self.assertEqual(date.weekday(), 0)  # Monday is 0

        # Dates should be consecutive weeks
        self.assertEqual(result[1] - result[0], timedelta(days=7))
        self.assertEqual(result[2] - result[1], timedelta(days=7))
