from django.test import TestCase

from dcim.choices import InterfaceTypeChoices
from dcim.forms import InterfaceImportForm
from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer, Site


class NetBoxModelImportFormCleanTestCase(TestCase):
    """
    Test the clean() method of NetBoxModelImportForm to ensure it properly converts
    empty strings to None for nullable fields during CSV import.
    Uses InterfaceImportForm as the concrete implementation to test.
    """

    @classmethod
    def setUpTestData(cls):
        # Create minimal test fixtures for Interface
        cls.site = Site.objects.create(name='Test Site', slug='test-site')
        cls.manufacturer = Manufacturer.objects.create(name='Test Manufacturer', slug='test-manufacturer')
        cls.device_type = DeviceType.objects.create(
            manufacturer=cls.manufacturer, model='Test Device Type', slug='test-device-type'
        )
        cls.device_role = DeviceRole.objects.create(name='Test Role', slug='test-role', color='ff0000')
        cls.device = Device.objects.create(
            name='Test Device', device_type=cls.device_type, role=cls.device_role, site=cls.site
        )
        # Create parent interfaces for ForeignKey testing
        cls.parent_interface = Interface.objects.create(
            device=cls.device, name='Parent Interface', type=InterfaceTypeChoices.TYPE_1GE_GBIC
        )
        cls.lag_interface = Interface.objects.create(
            device=cls.device, name='LAG Interface', type=InterfaceTypeChoices.TYPE_LAG
        )

    def test_empty_string_to_none_nullable_charfield(self):
        """Empty strings should convert to None for nullable CharField"""
        form = InterfaceImportForm(
            data={
                'device': self.device,
                'name': 'Interface 1',
                'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
                'duplex': '',  # nullable CharField
            }
        )
        self.assertTrue(form.is_valid(), f'Form errors: {form.errors}')
        self.assertIsNone(form.cleaned_data['duplex'])

    def test_empty_string_to_none_nullable_integerfield(self):
        """Empty strings should convert to None for nullable PositiveIntegerField"""
        form = InterfaceImportForm(
            data={
                'device': self.device,
                'name': 'Interface 2',
                'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
                'speed': '',  # nullable PositiveIntegerField
            }
        )
        self.assertTrue(form.is_valid(), f'Form errors: {form.errors}')
        self.assertIsNone(form.cleaned_data['speed'])

    def test_empty_string_to_none_nullable_smallintegerfield(self):
        """Empty strings should convert to None for nullable SmallIntegerField"""
        form = InterfaceImportForm(
            data={
                'device': self.device,
                'name': 'Interface 3',
                'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
                'tx_power': '',  # nullable SmallIntegerField
            }
        )
        self.assertTrue(form.is_valid(), f'Form errors: {form.errors}')
        self.assertIsNone(form.cleaned_data['tx_power'])

    def test_empty_string_to_none_nullable_decimalfield(self):
        """Empty strings should convert to None for nullable DecimalField"""
        form = InterfaceImportForm(
            data={
                'device': self.device,
                'name': 'Interface 4',
                'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
                'rf_channel_frequency': '',  # nullable DecimalField
                'rf_channel_width': '',  # nullable DecimalField
            }
        )
        self.assertTrue(form.is_valid(), f'Form errors: {form.errors}')
        self.assertIsNone(form.cleaned_data['rf_channel_frequency'])
        self.assertIsNone(form.cleaned_data['rf_channel_width'])

    def test_empty_string_to_none_nullable_foreignkey(self):
        """Empty strings should convert to None for nullable ForeignKey"""
        form = InterfaceImportForm(
            data={
                'device': self.device,
                'name': 'Interface 5',
                'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
                'lag': '',  # nullable ForeignKey
                'parent': '',  # nullable ForeignKey
                'bridge': '',  # nullable ForeignKey
                'vrf': '',  # nullable ForeignKey
            }
        )
        self.assertTrue(form.is_valid(), f'Form errors: {form.errors}')
        self.assertIsNone(form.cleaned_data['lag'])
        self.assertIsNone(form.cleaned_data['parent'])
        self.assertIsNone(form.cleaned_data['bridge'])
        self.assertIsNone(form.cleaned_data['vrf'])

    def test_empty_string_preserved_non_nullable_charfield(self):
        """Empty strings should be preserved for non-nullable CharField (blank=True only)"""
        form = InterfaceImportForm(
            data={
                'device': self.device,
                'name': 'Interface 6',
                'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
                'label': '',  # CharField with blank=True (not null=True)
                'description': '',  # CharField with blank=True (not null=True)
            }
        )
        self.assertTrue(form.is_valid(), f'Form errors: {form.errors}')
        # label and description are NOT nullable in the model, so empty string remains
        self.assertEqual(form.cleaned_data['label'], '')
        self.assertEqual(form.cleaned_data['description'], '')

    def test_empty_string_not_converted_for_required_fields(self):
        """Empty strings should NOT be converted for required fields"""
        form = InterfaceImportForm(
            data={
                'device': self.device,
                'name': '',  # required field, empty string should remain and cause error
                'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
            }
        )
        # Form should be invalid because name is required
        self.assertFalse(form.is_valid())
        if form.errors:
            self.assertIn('name', form.errors)

    def test_non_string_none_value_preserved(self):
        """None values should be preserved (not modified)"""
        form = InterfaceImportForm(
            data={
                'device': self.device,
                'name': 'Interface 7',
                'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
                'speed': None,  # Already None
                'tx_power': None,  # Already None
            }
        )
        self.assertTrue(form.is_valid(), f'Form errors: {form.errors}')
        self.assertIsNone(form.cleaned_data['speed'])
        self.assertIsNone(form.cleaned_data['tx_power'])

    def test_non_string_numeric_values_preserved(self):
        """Numeric values (including 0) should not be modified"""
        form = InterfaceImportForm(
            data={
                'device': self.device,
                'name': 'Interface 8',
                'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
                'speed': 0,  # nullable PositiveIntegerField with value 0
                'tx_power': 0,  # nullable SmallIntegerField with value 0
            }
        )
        self.assertTrue(form.is_valid(), f'Form errors: {form.errors}')
        self.assertEqual(form.cleaned_data['speed'], 0)
        self.assertEqual(form.cleaned_data['tx_power'], 0)

    def test_manytomany_fields_skipped(self):
        """ManyToMany fields should be skipped and not cause errors"""
        # Interface has 'vdcs' and 'wireless_lans' as M2M fields
        form = InterfaceImportForm(
            data={
                'device': self.device,
                'name': 'Interface 9',
                'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
                # vdcs and wireless_lans fields are M2M, handled by parent class
            }
        )
        self.assertTrue(form.is_valid(), f'Form errors: {form.errors}')

    def test_fields_not_in_cleaned_data_skipped(self):
        """Fields not present in cleaned_data should be skipped gracefully"""
        # Create minimal form data - some nullable fields won't be in cleaned_data
        form = InterfaceImportForm(
            data={
                'device': self.device,
                'name': 'Interface 10',
                'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
                # lag, parent, bridge, vrf, speed, etc. not provided
            }
        )
        # Should not raise KeyError when checking fields not in form data
        self.assertTrue(form.is_valid(), f'Form errors: {form.errors}')

    def test_valid_string_values_preserved(self):
        """Non-empty string values should be properly converted to their target types"""
        form = InterfaceImportForm(
            data={
                'device': self.device,
                'name': 'Interface 11',
                'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
                'speed': '1000000',  # Valid speed value (string will be converted to int)
                'mtu': '1500',  # Valid mtu value (string will be converted to int)
                'description': 'Test description',
            }
        )
        self.assertTrue(form.is_valid(), f'Form errors: {form.errors}')
        # speed and mtu are converted to int
        self.assertEqual(form.cleaned_data['speed'], 1000000)
        self.assertEqual(form.cleaned_data['mtu'], 1500)
        self.assertEqual(form.cleaned_data['description'], 'Test description')

    def test_multiple_nullable_fields_with_empty_strings(self):
        """Multiple nullable fields with empty strings should all convert to None"""
        form = InterfaceImportForm(
            data={
                'device': self.device,
                'name': 'Interface 12',
                'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
                'speed': '',  # nullable
                'duplex': '',  # nullable
                'tx_power': '',  # nullable
                'vrf': '',  # nullable ForeignKey
                'poe_mode': '',  # nullable
                'poe_type': '',  # nullable
            }
        )
        self.assertTrue(form.is_valid(), f'Form errors: {form.errors}')
        # All nullable fields should convert to None
        self.assertIsNone(form.cleaned_data['speed'])
        self.assertIsNone(form.cleaned_data['duplex'])
        self.assertIsNone(form.cleaned_data['tx_power'])
        self.assertIsNone(form.cleaned_data['vrf'])
        self.assertIsNone(form.cleaned_data['poe_mode'])
        self.assertIsNone(form.cleaned_data['poe_type'])

    def test_mixed_nullable_and_non_nullable_empty_strings(self):
        """Combination of nullable and non-nullable fields with empty strings"""
        form = InterfaceImportForm(
            data={
                'device': self.device,
                'name': 'Interface 13',
                'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
                'speed': '',  # nullable, should become None
                'label': '',  # NOT nullable (blank=True only), should remain empty string
                'duplex': '',  # nullable, should become None
                'description': '',  # NOT nullable (blank=True only), should remain empty string
            }
        )
        self.assertTrue(form.is_valid(), f'Form errors: {form.errors}')
        # Nullable fields convert to None
        self.assertIsNone(form.cleaned_data['speed'])
        self.assertIsNone(form.cleaned_data['duplex'])
        # Non-nullable fields remain empty strings
        self.assertEqual(form.cleaned_data['label'], '')
        self.assertEqual(form.cleaned_data['description'], '')

    def test_wireless_fields_nullable(self):
        """Wireless-specific nullable fields should convert empty strings to None"""
        form = InterfaceImportForm(
            data={
                'device': self.device,
                'name': 'Interface 14',
                'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
                'rf_role': '',  # nullable CharField
                'rf_channel': '',  # nullable CharField
                'rf_channel_frequency': '',  # nullable DecimalField
                'rf_channel_width': '',  # nullable DecimalField
            }
        )
        self.assertTrue(form.is_valid(), f'Form errors: {form.errors}')
        self.assertIsNone(form.cleaned_data['rf_role'])
        self.assertIsNone(form.cleaned_data['rf_channel'])
        self.assertIsNone(form.cleaned_data['rf_channel_frequency'])
        self.assertIsNone(form.cleaned_data['rf_channel_width'])

    def test_poe_fields_nullable(self):
        """PoE-specific nullable fields should convert empty strings to None"""
        form = InterfaceImportForm(
            data={
                'device': self.device,
                'name': 'Interface 15',
                'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
                'poe_mode': '',  # nullable CharField
                'poe_type': '',  # nullable CharField
            }
        )
        self.assertTrue(form.is_valid(), f'Form errors: {form.errors}')
        self.assertIsNone(form.cleaned_data['poe_mode'])
        self.assertIsNone(form.cleaned_data['poe_type'])

    def test_wwn_field_nullable(self):
        """WWN field (special field type) should convert empty string to None"""
        form = InterfaceImportForm(
            data={
                'device': self.device,
                'name': 'Interface 16',
                'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
                'wwn': '',  # nullable WWNField
            }
        )
        self.assertTrue(form.is_valid(), f'Form errors: {form.errors}')
        self.assertIsNone(form.cleaned_data['wwn'])
