from django import forms
from django.conf import settings
from django.utils.translation import gettext_lazy as _

from dcim.choices import InterfaceModeChoices
from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN
from dcim.forms.mixins import ScopedBulkEditForm
from dcim.models import Device, DeviceRole, Platform, Site
from extras.models import ConfigTemplate
from ipam.models import VLAN, VRF, VLANGroup, VLANTranslationPolicy
from netbox.forms import NetBoxModelBulkEditForm, OrganizationalModelBulkEditForm, PrimaryModelBulkEditForm
from netbox.forms.mixins import OwnerMixin
from tenancy.models import Tenant
from utilities.forms import add_blank_choice
from utilities.forms.fields import ChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
from utilities.forms.rendering import FieldSet
from utilities.forms.utils import get_capacity_unit_label
from utilities.forms.widgets import BulkEditNullBooleanSelect

from ..choices import *
from ..models import *

__all__ = (
    'ClusterBulkEditForm',
    'ClusterGroupBulkEditForm',
    'ClusterTypeBulkEditForm',
    'VMInterfaceBulkEditForm',
    'VirtualDiskBulkEditForm',
    'VirtualMachineBulkEditForm',
    'VirtualMachineTypeBulkEditForm',
)


class ClusterTypeBulkEditForm(OrganizationalModelBulkEditForm):
    model = ClusterType
    fieldsets = (
        FieldSet('description'),
    )
    nullable_fields = ('description', 'comments')


class ClusterGroupBulkEditForm(OrganizationalModelBulkEditForm):
    model = ClusterGroup
    fieldsets = (
        FieldSet('description'),
    )
    nullable_fields = ('description', 'comments')


class ClusterBulkEditForm(ScopedBulkEditForm, PrimaryModelBulkEditForm):
    type = DynamicModelChoiceField(
        label=_('Type'),
        queryset=ClusterType.objects.all(),
        required=False
    )
    group = DynamicModelChoiceField(
        label=_('Group'),
        queryset=ClusterGroup.objects.all(),
        required=False
    )
    status = ChoiceField(
        label=_('Status'),
        choices=add_blank_choice(ClusterStatusChoices),
        required=False,
        initial=''
    )
    tenant = DynamicModelChoiceField(
        label=_('Tenant'),
        queryset=Tenant.objects.all(),
        required=False
    )

    model = Cluster
    fieldsets = (
        FieldSet('type', 'group', 'status', 'tenant', 'description'),
        FieldSet('scope', name=_('Scope')),
    )
    nullable_fields = (
        'group', 'scope', 'tenant', 'description', 'comments',
    )


class VirtualMachineTypeBulkEditForm(PrimaryModelBulkEditForm):
    default_platform = DynamicModelChoiceField(
        label=_('Default platform'),
        queryset=Platform.objects.all(),
        required=False
    )
    default_vcpus = forms.IntegerField(
        label=_('Default vCPUs'),
        required=False,
    )
    default_memory = forms.IntegerField(
        label=_('Default memory'),
        required=False,
    )

    model = VirtualMachineType
    fieldsets = (
        FieldSet('description', name=_('Virtual Machine Type')),
        FieldSet('default_platform', 'default_vcpus', 'default_memory', name=_('Defaults')),
    )
    nullable_fields = (
        'default_platform', 'default_vcpus', 'default_memory', 'description', 'comments',
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Set unit label based on configured RAM_BASE_UNIT (MB vs MiB)
        self.fields['default_memory'].label = _('Default memory ({unit})').format(
            unit=get_capacity_unit_label(settings.RAM_BASE_UNIT)
        )


class VirtualMachineBulkEditForm(PrimaryModelBulkEditForm):
    virtual_machine_type = DynamicModelChoiceField(
        label=_('Virtual machine type'),
        queryset=VirtualMachineType.objects.all(),
        required=False
    )
    status = ChoiceField(
        label=_('Status'),
        choices=add_blank_choice(VirtualMachineStatusChoices),
        required=False,
        initial='',
    )
    start_on_boot = ChoiceField(
        label=_('Start on boot'),
        choices=add_blank_choice(VirtualMachineStartOnBootChoices),
        required=False,
        initial='',
    )
    site = DynamicModelChoiceField(
        label=_('Site'),
        queryset=Site.objects.all(),
        required=False
    )
    cluster = DynamicModelChoiceField(
        label=_('Cluster'),
        queryset=Cluster.objects.all(),
        required=False,
        query_params={
            'site_id': '$site'
        }
    )
    device = DynamicModelChoiceField(
        label=_('Device'),
        queryset=Device.objects.all(),
        required=False,
        query_params={
            'cluster_id': '$cluster',
            'site_id': '$site'
        }
    )
    role = DynamicModelChoiceField(
        label=_('Role'),
        queryset=DeviceRole.objects.filter(
            vm_role=True
        ),
        required=False,
        query_params={
            "vm_role": "True"
        }
    )
    tenant = DynamicModelChoiceField(
        label=_('Tenant'),
        queryset=Tenant.objects.all(),
        required=False
    )
    platform = DynamicModelChoiceField(
        label=_('Platform'),
        queryset=Platform.objects.all(),
        required=False
    )
    vcpus = forms.IntegerField(
        required=False,
        label=_('vCPUs')
    )
    memory = forms.IntegerField(
        required=False,
        label=_('Memory')
    )
    disk = forms.IntegerField(
        required=False,
        label=_('Disk')
    )
    config_template = DynamicModelChoiceField(
        queryset=ConfigTemplate.objects.all(),
        required=False
    )

    model = VirtualMachine
    fieldsets = (
        FieldSet('virtual_machine_type', 'status', 'start_on_boot', 'role', 'tenant', 'platform', 'description'),
        FieldSet('site', 'cluster', 'device', name=_('Placement')),
        FieldSet('vcpus', 'memory', 'disk', name=_('Resources')),
        FieldSet('config_template', name=_('Configuration')),
    )
    nullable_fields = (
        'virtual_machine_type', 'role', 'site', 'cluster', 'device', 'platform', 'vcpus', 'memory', 'disk', 'tenant',
        'description', 'comments',
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # The ?device=<id> GET param is navigation context (filter), not an intent to change the
        # device field — drop it from initial so Django's changed_data doesn't treat it as an edit.
        self.initial.pop('device', None)

        # Set unit labels based on configured RAM_BASE_UNIT / DISK_BASE_UNIT (MB vs MiB)
        self.fields['memory'].label = _('Memory ({unit})').format(unit=get_capacity_unit_label(settings.RAM_BASE_UNIT))
        self.fields['disk'].label = _('Disk ({unit})').format(unit=get_capacity_unit_label(settings.DISK_BASE_UNIT))


class VMInterfaceBulkEditForm(OwnerMixin, NetBoxModelBulkEditForm):
    virtual_machine = forms.ModelChoiceField(
        label=_('Virtual machine'),
        queryset=VirtualMachine.objects.all(),
        required=False,
        disabled=True,
        widget=forms.HiddenInput()
    )
    parent = DynamicModelChoiceField(
        label=_('Parent'),
        queryset=VMInterface.objects.all(),
        required=False
    )
    bridge = DynamicModelChoiceField(
        label=_('Bridge'),
        queryset=VMInterface.objects.all(),
        required=False
    )
    enabled = forms.NullBooleanField(
        label=_('Enabled'),
        required=False,
        widget=BulkEditNullBooleanSelect()
    )
    mtu = forms.IntegerField(
        required=False,
        min_value=INTERFACE_MTU_MIN,
        max_value=INTERFACE_MTU_MAX,
        label=_('MTU')
    )
    description = forms.CharField(
        label=_('Description'),
        max_length=100,
        required=False
    )
    mode = ChoiceField(
        label=_('Mode'),
        choices=add_blank_choice(InterfaceModeChoices),
        required=False
    )
    vlan_group = DynamicModelChoiceField(
        queryset=VLANGroup.objects.all(),
        required=False,
        label=_('VLAN group')
    )
    untagged_vlan = DynamicModelChoiceField(
        queryset=VLAN.objects.all(),
        required=False,
        query_params={
            'group_id': '$vlan_group',
        },
        label=_('Untagged VLAN')
    )
    tagged_vlans = DynamicModelMultipleChoiceField(
        queryset=VLAN.objects.all(),
        required=False,
        query_params={
            'group_id': '$vlan_group',
        },
        label=_('Tagged VLANs')
    )
    vrf = DynamicModelChoiceField(
        queryset=VRF.objects.all(),
        required=False,
        label=_('VRF')
    )
    vlan_translation_policy = DynamicModelChoiceField(
        queryset=VLANTranslationPolicy.objects.all(),
        required=False,
        label=_('VLAN Translation Policy')
    )

    model = VMInterface
    fieldsets = (
        FieldSet('mtu', 'enabled', 'vrf', 'description'),
        FieldSet('parent', 'bridge', name=_('Related Interfaces')),
        FieldSet(
            'mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans', 'vlan_translation_policy',
            name=_('802.1Q Switching')
        ),
    )
    nullable_fields = (
        'parent', 'bridge', 'mtu', 'vrf', 'description', 'vlan_translation_policy',
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if 'virtual_machine' in self.initial:
            vm_id = self.initial.get('virtual_machine')

            # Restrict parent/bridge interface assignment by VM
            self.fields['parent'].widget.add_query_param('virtual_machine_id', vm_id)
            self.fields['bridge'].widget.add_query_param('virtual_machine_id', vm_id)

            # Limit VLAN choices by virtual machine
            self.fields['untagged_vlan'].widget.add_query_param('available_on_virtualmachine', vm_id)
            self.fields['tagged_vlans'].widget.add_query_param('available_on_virtualmachine', vm_id)

        else:
            # See 5643
            if 'pk' in self.initial:
                site = None
                interfaces = VMInterface.objects.filter(
                    pk__in=self.initial['pk']
                ).prefetch_related(
                    'virtual_machine__site',
                    'virtual_machine__cluster',
                    'virtual_machine__device',
                )

                # Determine the effective site for each interface's VM (from its site,
                # cluster, or device). If all selected interfaces share the same site,
                # use it to filter VLAN choices; otherwise leave unfiltered.
                for interface in interfaces:
                    vm = interface.virtual_machine
                    vm_site = (
                        vm.site
                        or (vm.cluster and vm.cluster._site)
                        or (vm.device and vm.device.site)
                    )
                    if site is None:
                        site = vm_site
                    elif vm_site is not site:
                        site = None
                        break

                if site is not None:
                    self.fields['untagged_vlan'].widget.add_query_param('site_id', site.pk)
                    self.fields['tagged_vlans'].widget.add_query_param('site_id', site.pk)

            self.fields['parent'].choices = ()
            self.fields['parent'].widget.attrs['disabled'] = True
            self.fields['bridge'].choices = ()
            self.fields['bridge'].widget.attrs['disabled'] = True


class VirtualDiskBulkEditForm(OwnerMixin, NetBoxModelBulkEditForm):
    virtual_machine = forms.ModelChoiceField(
        label=_('Virtual machine'),
        queryset=VirtualMachine.objects.all(),
        required=False,
        disabled=True,
        widget=forms.HiddenInput()
    )
    size = forms.IntegerField(
        required=False,
        label=_('Size')
    )
    description = forms.CharField(
        label=_('Description'),
        max_length=100,
        required=False
    )

    model = VirtualDisk
    fieldsets = (
        FieldSet('size', 'description'),
    )
    nullable_fields = ('description',)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Set unit label based on configured DISK_BASE_UNIT (MB vs MiB)
        self.fields['size'].label = _('Size ({unit})').format(unit=get_capacity_unit_label(settings.DISK_BASE_UNIT))
