import copy

from core.choices import DataSourceStatusChoices
from django import forms
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.utils import timezone
from django.utils.translation import gettext as _
from extras.choices import DurationChoices
from netbox.forms import NetBoxModelFilterSetForm
from netbox.forms import NetBoxModelForm
from packaging import version
from utilities.forms import add_blank_choice
from utilities.forms import BootstrapMixin
from utilities.forms import FilterForm
from utilities.forms import get_field_value
from utilities.forms.fields import CommentField
from utilities.forms.fields import DynamicModelChoiceField
from utilities.forms.fields import DynamicModelMultipleChoiceField
from utilities.forms.widgets import APISelectMultiple
from utilities.forms.widgets import DateTimePicker
from utilities.forms.widgets import HTMXSelect
from utilities.forms.widgets import NumberWithOptions
from utilities.utils import local_now

from .choices import IPFabricSnapshotStatusModelChoices
from .choices import transform_field_source_columns
from .models import IPFabricBranch
from .models import IPFabricRelationshipField
from .models import IPFabricSnapshot
from .models import IPFabricSource
from .models import IPFabricSync
from .models import IPFabricTransformField
from .models import IPFabricTransformMap

NETBOX_CURRENT_VERSION = version.parse(settings.VERSION)

if NETBOX_CURRENT_VERSION >= version.parse("3.7.0"):
    from netbox.forms.mixins import SavedFiltersMixin
else:
    from extras.forms.mixins import SavedFiltersMixin

exclude_fields = [
    "id",
    "created",
    "last_updated",
    "custom_field_data",
    "_name",
    "status",
]


class IPFSiteChoiceField(forms.MultipleChoiceField):
    def valid_value(self, value):
        """Check to see if the provided value is a valid choice."""
        text_value = str(value)
        for k, v in self.choices:
            if isinstance(v, (list, tuple)):
                for k2, v2 in v:
                    if value == k2 or text_value == str(k2):
                        return True
            else:
                if value == k or text_value == str(k):
                    return True
        return False


dcim_parameters = {
    "site": forms.BooleanField(required=False, label=_("Sites"), initial=True),
    "manufacturer": forms.BooleanField(
        required=False, label=_("Manufacturers"), initial=True
    ),
    "devicetype": forms.BooleanField(
        required=False, label=_("Device Types"), initial=True
    ),
    "devicerole": forms.BooleanField(
        required=False, label=_("Device Roles"), initial=True
    ),
    "platform": forms.BooleanField(required=False, label=_("Platforms"), initial=True),
    "device": forms.BooleanField(required=False, label=_("Devices"), initial=True),
    "virtualchassis": forms.BooleanField(
        required=False, label=_("Virtual Chassis"), initial=True
    ),
    "interface": forms.BooleanField(required=False, label=_("Interfaces")),
    # TODO: Inventory Item broken util https://github.com/netbox-community/netbox/issues/13422 is resolved
    # "inventoryitem": forms.BooleanField(required=False, label=_("Part Numbers")),
}
ipam_parameters = {
    "vlan": forms.BooleanField(required=False, label=_("VLANs")),
    "vrf": forms.BooleanField(required=False, label=_("VRFs")),
    "prefix": forms.BooleanField(required=False, label=_("Prefixes")),
    "ipaddress": forms.BooleanField(required=False, label=_("IP Addresses")),
}
sync_parameters = {"dcim": dcim_parameters, "ipam": ipam_parameters}


def source_column_choices(model):
    columns = transform_field_source_columns.get(model, None)
    if columns:
        choices = [(f, f) for f in transform_field_source_columns.get(model)]
    else:
        choices = []
    return choices


def add_all_sites(choices):
    """
    Add a blank choice to the beginning of a choices list.
    """
    return ((None, "All Sites"),) + tuple(choices)


def str_to_list(str):
    if not isinstance(str, list):
        return [str]
    else:
        return str


def list_to_choices(choices):
    new_choices = ()
    for choice in choices:
        new_choices = new_choices + ((choice, choice),)
    return new_choices


class IPFabricRelationshipFieldForm(BootstrapMixin, forms.ModelForm):
    coalesce = forms.BooleanField(required=False, initial=False)
    target_field = forms.CharField(
        label="Target Field",
        required=True,
        help_text="Select target model field.",
        widget=forms.Select(),
    )

    fieldsets = (
        (
            "Transform Map",
            ("transform_map", "source_model", "target_field", "coalesce"),
        ),
        ("Extras", ("template",)),
    )

    class Meta:
        model = IPFabricRelationshipField
        fields = (
            "transform_map",
            "source_model",
            "target_field",
            "coalesce",
            "template",
        )
        widgets = {
            "transform_map": HTMXSelect(),
        }
        help_texts = {
            "link_text": _(
                "Jinja2 template code for the source field. Reference the object as <code>{{ object }}</code>. "
                "templates which render as empty text will not be displayed."
            ),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if not self.data:
            if self.instance and self.instance.pk is not None:
                fields = (
                    self.instance.transform_map.target_model.model_class()._meta.fields
                )
                self.fields["target_field"].widget.choices = add_blank_choice(
                    [
                        (f.name, f.verbose_name)
                        for f in fields
                        if f.is_relation and f.name not in exclude_fields
                    ]
                )
                self.fields["target_field"].widget.initial = self.instance.target_field
            else:
                if kwargs["initial"].get("transform_map", None):
                    transform_map_id = kwargs["initial"]["transform_map"]
                    transform_map = IPFabricTransformMap.objects.get(
                        pk=transform_map_id
                    )
                    fields = transform_map.target_model.model_class()._meta.fields
                    choices = [
                        (f.name, f.verbose_name)
                        for f in fields
                        if f.is_relation and f.name not in exclude_fields
                    ]
                    self.fields["target_field"].widget.choices = add_blank_choice(
                        choices
                    )


class IPFabricTransformFieldForm(BootstrapMixin, forms.ModelForm):
    coalesce = forms.BooleanField(required=False, initial=False)
    source_field = forms.CharField(
        label="Source Field",
        required=True,
        help_text="Select column from IP Fabric.",
        widget=forms.Select(),
    )
    target_field = forms.CharField(
        label="Target Field",
        required=True,
        help_text="Select target model field.",
        widget=forms.Select(),
    )

    fieldsets = (
        (
            "Transform Map",
            ("transform_map", "source_field", "target_field", "coalesce"),
        ),
        ("Extras", ("template",)),
    )

    class Meta:
        model = IPFabricTransformField
        fields = (
            "transform_map",
            "source_field",
            "target_field",
            "coalesce",
            "template",
        )
        widgets = {
            "template": forms.Textarea(attrs={"class": "font-monospace"}),
            "transform_map": HTMXSelect(),
        }
        help_texts = {
            "link_text": _(
                "Jinja2 template code for the source field. Reference the object as <code>{{ object }}</code>. "
                "templates which render as empty text will not be displayed."
            ),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if not self.data:
            if self.instance and self.instance.pk is not None:
                fields = (
                    self.instance.transform_map.target_model.model_class()._meta.fields
                )
                source_fields = self.instance.transform_map.source_model
                self.fields["target_field"].widget.choices = add_blank_choice(
                    [
                        (f.name, f.verbose_name)
                        for f in fields
                        if not f.is_relation and f.name not in exclude_fields
                    ]
                )
                self.fields["target_field"].widget.initial = self.instance.target_field
                self.fields["source_field"].widget.choices = add_blank_choice(
                    source_column_choices(source_fields)
                )
            else:
                if kwargs["initial"].get("transform_map", None):
                    transform_map_id = kwargs["initial"]["transform_map"]
                    transform_map = IPFabricTransformMap.objects.get(
                        pk=transform_map_id
                    )
                    fields = transform_map.target_model.model_class()._meta.fields
                    choices = [
                        (f.name, f.verbose_name)
                        for f in fields
                        if not f.is_relation and f.name not in exclude_fields
                    ]
                    self.fields["target_field"].widget.choices = add_blank_choice(
                        choices
                    )
                    self.fields["source_field"].widget.choices = add_blank_choice(
                        source_column_choices(transform_map.source_model)
                    )


class IPFabricTransformMapForm(BootstrapMixin, forms.ModelForm):
    status = forms.CharField(
        required=False,
        label=_("Status"),
        widget=forms.Select(),
    )

    class Meta:
        model = IPFabricTransformMap
        fields = ("name", "source_model", "target_model", "status")
        widgets = {
            "target_model": HTMXSelect(hx_url="/plugins/ipfabric/transform-map/add"),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if not self.data:
            if kwargs["initial"].get("target_model"):
                target_model = ContentType.objects.get(
                    pk=kwargs["initial"].get("target_model")
                )
                try:
                    status = target_model.model_class()._meta.get_field("status")
                    self.fields["status"].widget.choices = add_blank_choice(
                        status.choices
                    )
                    self.fields["status"].widget.initial = get_field_value(
                        self, "status"
                    )
                except Exception as e:  # noqa: F841
                    self.fields["status"].widget.attrs["disabled"] = "disabled"
            else:
                if self.instance and self.instance.pk is not None:
                    transform_map = self.instance.target_model.model_class()
                    try:
                        status = transform_map._meta.get_field("status")
                        self.fields["status"].widget.choices = add_blank_choice(
                            status.choices
                        )
                        self.fields["status"].widget.initial = get_field_value(
                            self, "status"
                        )
                    except Exception as e:  # noqa: F841
                        self.fields["status"].widget.attrs["disabled"] = "disabled"


class IPFabricSnapshotFilterForm(NetBoxModelFilterSetForm):
    model = IPFabricSnapshot
    fieldsets = (
        (None, ("q", "filter_id")),
        ("Source", ("name", "source_id", "status", "snapshot_id")),
    )
    name = forms.CharField(required=False, label=_("Name"))
    status = forms.CharField(required=False, label=_("Status"))
    source_id = DynamicModelMultipleChoiceField(
        queryset=IPFabricSource.objects.all(), required=False, label=_("Source")
    )
    snapshot_id = forms.CharField(required=False, label=_("Snapshot ID"))


class IPFabricSourceFilterForm(NetBoxModelFilterSetForm):
    model = IPFabricSource
    fieldsets = (
        (None, ("q", "filter_id")),
        ("Data Source", ("status",)),
    )
    status = forms.MultipleChoiceField(choices=DataSourceStatusChoices, required=False)


class IPFabricBranchFilterForm(SavedFiltersMixin, FilterForm):
    fieldsets = (
        (None, ("q", "filter_id")),
        ("Source", ("sync_id",)),
    )
    model = IPFabricBranch
    sync_id = DynamicModelMultipleChoiceField(
        queryset=IPFabricSync.objects.all(), required=False, label=_("Sync")
    )


class IPFabricSourceForm(NetBoxModelForm):
    comments = CommentField()

    class Meta:
        model = IPFabricSource
        fields = [
            "name",
            "type",
            "url",
            "description",
            "comments",
        ]
        widgets = {
            "type": HTMXSelect(),
        }

    @property
    def fieldsets(self):
        fieldsets = [
            (_("Source"), ("name", "type", "url")),
        ]
        if self.source_type == "local":
            fieldsets.append((_("Parameters"), ("auth", "verify")))

        return fieldsets

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.source_type = get_field_value(self, "type")

        if self.source_type == "local":
            self.fields["auth"] = forms.CharField(
                required=True,
                label=_("API Token"),
                widget=forms.TextInput(attrs={"class": "form-control"}),
                help_text=_("IP Fabric API Token."),
            )
            self.fields["verify"] = forms.BooleanField(
                required=False,
                initial=True,
                help_text=_(
                    "Certificate validation. Uncheck if using self signed certificate."
                ),
            )
            if self.instance.pk:
                for name, form_field in self.instance.parameters.items():
                    self.fields[name].initial = self.instance.parameters.get(name)

    def save(self, *args, **kwargs):
        parameters = {}
        for name in self.fields:
            if name.startswith("auth"):
                parameters["auth"] = self.cleaned_data[name]
            if name.startswith("verify"):
                parameters["verify"] = self.cleaned_data[name]

        self.instance.parameters = parameters
        self.instance.status = DataSourceStatusChoices.NEW

        instance = super().save(*args, **kwargs)

        if instance.type == "remote":
            if not IPFabricSnapshot.objects.filter(
                source=instance, snapshot_id="$last"
            ).exists():
                IPFabricSnapshot.objects.create(
                    source=instance,
                    name="$last",
                    snapshot_id="$last",
                    status=IPFabricSnapshotStatusModelChoices.STATUS_LOADED,
                    last_updated=timezone.now(),
                )

        return instance


class IPFabricSyncForm(NetBoxModelForm):
    source = forms.ModelChoiceField(
        queryset=IPFabricSource.objects.all(),
        required=True,
        label=_("IP Fabric Source"),
        widget=HTMXSelect(),
    )
    snapshot_data = DynamicModelChoiceField(
        queryset=IPFabricSnapshot.objects.filter(status="loaded"),
        required=True,
        label=_("Snapshot"),
        query_params={
            "source_id": "$source",
            "status": "loaded",
        },
    )

    sites = forms.MultipleChoiceField(
        required=False,
        label=_("Sites"),
        widget=APISelectMultiple(
            api_url="/api/plugins/ipfabric/snapshot/{{snapshot_data}}/sites/",
        ),
    )

    scheduled = forms.DateTimeField(
        required=False,
        widget=DateTimePicker(),
        label=_("Schedule at"),
        help_text=_("Schedule execution of sync to a set time"),
    )
    interval = forms.IntegerField(
        required=False,
        min_value=1,
        label=_("Recurs every"),
        widget=NumberWithOptions(options=DurationChoices),
        help_text=_("Interval at which this sync is re-run (in minutes)"),
    )
    auto_merge = forms.BooleanField(
        required=False,
        label=_("Auto Merge"),
        help_text=_("Automatically merge staged changes into NetBox"),
    )

    class Meta:
        model = IPFabricSync
        fields = (
            "name",
            "source",
            "snapshot_data",
            "auto_merge",
            "sites",
            "type",
            "tags",
            "scheduled",
            "interval",
        )
        widgets = {
            "source": HTMXSelect(),
            "type": HTMXSelect(),
        }

    @property
    def fieldsets(self):
        fieldsets = [
            (
                "IP Fabric Source",
                (
                    "name",
                    "source",
                ),
            ),
        ]
        if self.source_type == "local":
            fieldsets.append(
                (
                    "Snapshot Information",
                    ("snapshot_data", "sites"),
                )
            )
        else:
            fieldsets.append(
                (
                    "Snapshot Information",
                    ("snapshot_data",),
                )
            )
        fieldsets.append(("Ingestion Type", ("type",)))
        if self.backend_fields:
            for k, v in self.backend_fields.items():
                fieldsets.append((f"{k.upper()} Parameters", v))
        fieldsets.append(("Ingestion Execution Parameters", ("scheduled", "interval")))
        fieldsets.append(("Extras", ("auto_merge",)))
        fieldsets.append(("Tags", ("tags",)))

        return fieldsets

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.source_type = None
        ingestion_type = get_field_value(self, "source")

        if not self.data:
            if ingestion_type:
                self.source_type = IPFabricSource.objects.get(pk=ingestion_type).type
            if sites := get_field_value(self, "sites"):
                sites = list_to_choices(str_to_list(sites))
                self.fields["sites"].choices = sites
                self.fields["sites"].initial = sites
        else:
            if snapshot_id := self.data.get("snapshot_data"):
                snapshot_sites = IPFabricSnapshot.objects.get(pk=snapshot_id).sites
                choices = list_to_choices(str_to_list(snapshot_sites))
                self.fields["sites"].choices = choices

        if self.instance and self.instance.pk:
            if not kwargs.get("initial"):
                self.source_type = self.instance.snapshot_data.source.type
                self.initial["source"] = self.instance.snapshot_data.source
                if not self.data:
                    self.fields["sites"].choices = list_to_choices(
                        self.instance.snapshot_data.sites
                    )

                self.initial["sites"] = self.instance.parameters["sites"]

        backend_type = get_field_value(self, "type")
        backend = {}
        if backend_type == "all":
            backend = sync_parameters
        else:
            backend[backend_type] = sync_parameters.get(backend_type)

        now = local_now().strftime("%Y-%m-%d %H:%M:%S")
        self.fields["scheduled"].help_text += f" (current time: <strong>{now}</strong>)"

        # Add backend-specific form fields
        self.backend_fields = {}
        for k, v in backend.items():
            self.backend_fields[k] = []
            for name, form_field in v.items():
                field_name = f"ipf_{name}"
                self.backend_fields[k].append(field_name)
                self.fields[field_name] = copy.copy(form_field)
                if self.instance and self.instance.parameters:
                    self.fields[field_name].initial = self.instance.parameters.get(name)

    def clean(self):
        super().clean()
        snapshot = self.cleaned_data["snapshot_data"]

        sites = self.data.get("sites")
        choices = list_to_choices(str_to_list(sites))
        self.fields["sites"].choices = choices

        if sites:
            if not any(y in x for x in snapshot.sites for y in sites):
                raise ValidationError({"sites": f"{sites} not part of the snapshot."})

        scheduled_time = self.cleaned_data.get("scheduled")
        if scheduled_time and scheduled_time < local_now():
            raise forms.ValidationError(_("Scheduled time must be in the future."))

        # When interval is used without schedule at, schedule for the current time
        if self.cleaned_data.get("interval") and not scheduled_time:
            self.cleaned_data["scheduled"] = local_now()

        return self.cleaned_data

    def save(self, *args, **kwargs):
        parameters = {}
        for name in self.fields:
            if name.startswith("ipf_"):
                parameters[name[4:]] = self.cleaned_data[name]
            if name == "sites":
                parameters["sites"] = self.cleaned_data["sites"]
        self.instance.parameters = parameters
        self.instance.status = DataSourceStatusChoices.NEW

        object = super().save(*args, **kwargs)
        if object.scheduled:
            object.enqueue_sync_job()
        return object


# class SyncForm(forms.Form):
#     def __init__(self, *args, **kwargs):
#         self.snapshots = kwargs.pop("snapshot_choices", None)
#         self.sites = kwargs.pop("site_choices", None)
#         super(SyncForm, self).__init__(*args, **kwargs)
#         if self.snapshots:
#             snapshot_choices = [
#                 (snapshot_id, snapshot_name)
#                 for snapshot_name, snapshot_id in self.snapshots.values()
#             ]
#             self.fields["snapshot"] = forms.ChoiceField(
#                 label="Snapshot",
#                 required=True,
#                 choices=snapshot_choices,
#                 help_text="IPFabric snapshot to sync from. Defaults to $last",
#                 widget=forms.Select(
#                     attrs={
#                         "hx-get": reverse("plugins:ipfabric_netbox:ipfabricsync_add"),
#                         "hx-trigger": "change",
#                         "hx-target": "#modules",
#                         "class": "form-control",
#                     }
#                 ),
#             )
#         if self.sites:
#             site_choices = [(site, site) for site in self.sites]
#             self.fields["site"] = forms.ChoiceField(
#                 label="Site",
#                 required=False,
#                 choices=add_blank_choice(site_choices),
#                 help_text="Sites available within snapshot",
#                 widget=forms.Select(attrs={"class": "form-control"}),
#             )
#         else:
#             self.fields["site"] = forms.ChoiceField(
#                 label="Site",
#                 required=False,
#                 choices=add_blank_choice([]),
#                 help_text="Sites available within snapshot",
#                 widget=forms.Select(
#                     attrs={"class": "form-control", "disabled": "disabled"}
#                 ),
#             )

tableChoices = [
    ("eol_details", "Inventory - EOL_DETAILS"),
    ("fans", "Inventory - FANS"),
    ("interfaces", "Inventory - INTERFACES"),
    ("modules", "Inventory - MODULES"),
    ("pn", "Inventory - PN"),
    ("addressing.arp_table", "Addressing - ARP_TABLE"),
    ("addressing.ipv6_neighbor_discovery", "Addressing - IPV6_NEIGHBOR_DISCOVERY"),
    ("addressing.mac_table", "Addressing - MAC_TABLE"),
    ("addressing.managed_ip_ipv4", "Addressing - MANAGED_IP_IPV4"),
    ("addressing.managed_ip_ipv6", "Addressing - MANAGED_IP_IPV6"),
    ("addressing.nat44", "Addressing - NAT44"),
    ("cloud.virtual_interfaces", "Cloud - VIRTUAL_INTERFACES"),
    ("cloud.virtual_machines", "Cloud - VIRTUAL_MACHINES"),
    ("dhcp.relay_global_stats_received", "Dhcp - RELAY_GLOBAL_STATS_RECEIVED"),
    ("dhcp.relay_global_stats_relayed", "Dhcp - RELAY_GLOBAL_STATS_RELAYED"),
    ("dhcp.relay_global_stats_sent", "Dhcp - RELAY_GLOBAL_STATS_SENT"),
    ("dhcp.relay_global_stats_summary", "Dhcp - RELAY_GLOBAL_STATS_SUMMARY"),
    ("dhcp.relay_interfaces", "Dhcp - RELAY_INTERFACES"),
    ("dhcp.relay_interfaces_stats_received", "Dhcp - RELAY_INTERFACES_STATS_RECEIVED"),
    ("dhcp.relay_interfaces_stats_relayed", "Dhcp - RELAY_INTERFACES_STATS_RELAYED"),
    ("dhcp.relay_interfaces_stats_sent", "Dhcp - RELAY_INTERFACES_STATS_SENT"),
    ("dhcp.server_excluded_interfaces", "Dhcp - SERVER_EXCLUDED_INTERFACES"),
    ("dhcp.server_excluded_ranges", "Dhcp - SERVER_EXCLUDED_RANGES"),
    ("dhcp.server_leases", "Dhcp - SERVER_LEASES"),
    ("dhcp.server_pools", "Dhcp - SERVER_POOLS"),
    ("dhcp.server_summary", "Dhcp - SERVER_SUMMARY"),
    ("fhrp.glbp_forwarders", "Fhrp - GLBP_FORWARDERS"),
    ("fhrp.group_state", "Fhrp - GROUP_STATE"),
    ("fhrp.stproot_alignment", "Fhrp - STPROOT_ALIGNMENT"),
    ("fhrp.virtual_gateways", "Fhrp - VIRTUAL_GATEWAYS"),
    (
        "interfaces.average_rates_data_bidirectional",
        "Interfaces - AVERAGE_RATES_DATA_BIDIRECTIONAL",
    ),
    (
        "interfaces.average_rates_data_bidirectional_per_device",
        "Interfaces - AVERAGE_RATES_DATA_BIDIRECTIONAL_PER_DEVICE",
    ),
    (
        "interfaces.average_rates_data_inbound",
        "Interfaces - AVERAGE_RATES_DATA_INBOUND",
    ),
    (
        "interfaces.average_rates_data_inbound_per_device",
        "Interfaces - AVERAGE_RATES_DATA_INBOUND_PER_DEVICE",
    ),
    (
        "interfaces.average_rates_data_outbound",
        "Interfaces - AVERAGE_RATES_DATA_OUTBOUND",
    ),
    (
        "interfaces.average_rates_data_outbound_per_device",
        "Interfaces - AVERAGE_RATES_DATA_OUTBOUND_PER_DEVICE",
    ),
    (
        "interfaces.average_rates_drops_bidirectional",
        "Interfaces - AVERAGE_RATES_DROPS_BIDIRECTIONAL",
    ),
    (
        "interfaces.average_rates_drops_bidirectional_per_device",
        "Interfaces - AVERAGE_RATES_DROPS_BIDIRECTIONAL_PER_DEVICE",
    ),
    (
        "interfaces.average_rates_drops_inbound",
        "Interfaces - AVERAGE_RATES_DROPS_INBOUND",
    ),
    (
        "interfaces.average_rates_drops_inbound_per_device",
        "Interfaces - AVERAGE_RATES_DROPS_INBOUND_PER_DEVICE",
    ),
    (
        "interfaces.average_rates_drops_outbound",
        "Interfaces - AVERAGE_RATES_DROPS_OUTBOUND",
    ),
    (
        "interfaces.average_rates_drops_outbound_per_device",
        "Interfaces - AVERAGE_RATES_DROPS_OUTBOUND_PER_DEVICE",
    ),
    (
        "interfaces.average_rates_errors_bidirectional",
        "Interfaces - AVERAGE_RATES_ERRORS_BIDIRECTIONAL",
    ),
    (
        "interfaces.average_rates_errors_bidirectional_per_device",
        "Interfaces - AVERAGE_RATES_ERRORS_BIDIRECTIONAL_PER_DEVICE",
    ),
    (
        "interfaces.average_rates_errors_inbound",
        "Interfaces - AVERAGE_RATES_ERRORS_INBOUND",
    ),
    (
        "interfaces.average_rates_errors_inbound_per_device",
        "Interfaces - AVERAGE_RATES_ERRORS_INBOUND_PER_DEVICE",
    ),
    (
        "interfaces.average_rates_errors_outbound",
        "Interfaces - AVERAGE_RATES_ERRORS_OUTBOUND",
    ),
    (
        "interfaces.average_rates_errors_outbound_per_device",
        "Interfaces - AVERAGE_RATES_ERRORS_OUTBOUND_PER_DEVICE",
    ),
    (
        "interfaces.connectivity_matrix_unmanaged_neighbors_detail",
        "Interfaces - CONNECTIVITY_MATRIX_UNMANAGED_NEIGHBORS_DETAIL",
    ),
    (
        "interfaces.connectivity_matrix_unmanaged_neighbors_summary",
        "Interfaces - CONNECTIVITY_MATRIX_UNMANAGED_NEIGHBORS_SUMMARY",
    ),
    ("interfaces.counters_inbound", "Interfaces - COUNTERS_INBOUND"),
    ("interfaces.counters_outbound", "Interfaces - COUNTERS_OUTBOUND"),
    (
        "interfaces.current_rates_data_bidirectional",
        "Interfaces - CURRENT_RATES_DATA_BIDIRECTIONAL",
    ),
    (
        "interfaces.current_rates_data_inbound",
        "Interfaces - CURRENT_RATES_DATA_INBOUND",
    ),
    (
        "interfaces.current_rates_data_outbound",
        "Interfaces - CURRENT_RATES_DATA_OUTBOUND",
    ),
    ("interfaces.err_disabled", "Interfaces - ERR_DISABLED"),
    (
        "interfaces.point_to_point_over_ethernet",
        "Interfaces - POINT_TO_POINT_OVER_ETHERNET",
    ),
    (
        "interfaces.point_to_point_over_ethernet_sessions",
        "Interfaces - POINT_TO_POINT_OVER_ETHERNET_SESSIONS",
    ),
    ("interfaces.storm_control_all", "Interfaces - STORM_CONTROL_ALL"),
    ("interfaces.storm_control_broadcast", "Interfaces - STORM_CONTROL_BROADCAST"),
    ("interfaces.storm_control_multicast", "Interfaces - STORM_CONTROL_MULTICAST"),
    ("interfaces.storm_control_unicast", "Interfaces - STORM_CONTROL_UNICAST"),
    ("interfaces.switchport", "Interfaces - SWITCHPORT"),
    ("interfaces.transceivers", "Interfaces - TRANSCEIVERS"),
    ("interfaces.transceivers_errors", "Interfaces - TRANSCEIVERS_ERRORS"),
    ("interfaces.transceivers_statistics", "Interfaces - TRANSCEIVERS_STATISTICS"),
    (
        "interfaces.transceivers_triggered_thresholds",
        "Interfaces - TRANSCEIVERS_TRIGGERED_THRESHOLDS",
    ),
    ("interfaces.tunnels_ipv4", "Interfaces - TUNNELS_IPV4"),
    ("interfaces.tunnels_ipv6", "Interfaces - TUNNELS_IPV6"),
    ("load_balancing.virtual_servers", "Load_balancing - VIRTUAL_SERVERS"),
    (
        "load_balancing.virtual_servers_f5_partitions",
        "Load_balancing - VIRTUAL_SERVERS_F5_PARTITIONS",
    ),
    (
        "load_balancing.virtual_servers_pool_members",
        "Load_balancing - VIRTUAL_SERVERS_POOL_MEMBERS",
    ),
    ("load_balancing.virtual_servers_pools", "Load_balancing - VIRTUAL_SERVERS_POOLS"),
    ("management.aaa_accounting", "Management - AAA_ACCOUNTING"),
    ("management.aaa_authentication", "Management - AAA_AUTHENTICATION"),
    ("management.aaa_authorization", "Management - AAA_AUTHORIZATION"),
    ("management.aaa_lines", "Management - AAA_LINES"),
    ("management.aaa_password_strength", "Management - AAA_PASSWORD_STRENGTH"),
    ("management.aaa_servers", "Management - AAA_SERVERS"),
    ("management.aaa_users", "Management - AAA_USERS"),
    (
        "management.cisco_smart_licenses_authorization",
        "Management - CISCO_SMART_LICENSES_AUTHORIZATION",
    ),
    (
        "management.cisco_smart_licenses_registration",
        "Management - CISCO_SMART_LICENSES_REGISTRATION",
    ),
    (
        "management.cisco_smart_licenses_reservations",
        "Management - CISCO_SMART_LICENSES_RESERVATIONS",
    ),
    ("management.dns_resolver_servers", "Management - DNS_RESOLVER_SERVERS"),
    ("management.dns_resolver_settings", "Management - DNS_RESOLVER_SETTINGS"),
    ("management.flow_overview", "Management - FLOW_OVERVIEW"),
    ("management.license_summary", "Management - LICENSE_SUMMARY"),
    ("management.licenses", "Management - LICENSES"),
    ("management.licenses_detail", "Management - LICENSES_DETAIL"),
    ("management.logging_local", "Management - LOGGING_LOCAL"),
    ("management.logging_remote", "Management - LOGGING_REMOTE"),
    ("management.logging_summary", "Management - LOGGING_SUMMARY"),
    ("management.netflow_collectors", "Management - NETFLOW_COLLECTORS"),
    ("management.netflow_devices", "Management - NETFLOW_DEVICES"),
    ("management.netflow_interfaces", "Management - NETFLOW_INTERFACES"),
    ("management.ntp_sources", "Management - NTP_SOURCES"),
    ("management.ntp_summary", "Management - NTP_SUMMARY"),
    ("management.port_mirroring", "Management - PORT_MIRRORING"),
    ("management.ptp_interfaces", "Management - PTP_INTERFACES"),
    ("management.ptp_local_clock", "Management - PTP_LOCAL_CLOCK"),
    ("management.ptp_masters", "Management - PTP_MASTERS"),
    ("management.saved_config_consistency", "Management - SAVED_CONFIG_CONSISTENCY"),
    ("management.sflow_collectors", "Management - SFLOW_COLLECTORS"),
    ("management.sflow_devices", "Management - SFLOW_DEVICES"),
    ("management.sflow_sources", "Management - SFLOW_SOURCES"),
    ("management.snmp_communities", "Management - SNMP_COMMUNITIES"),
    ("management.snmp_summary", "Management - SNMP_SUMMARY"),
    ("management.snmp_trap_hosts", "Management - SNMP_TRAP_HOSTS"),
    ("management.snmp_users", "Management - SNMP_USERS"),
    ("management.telnet_access", "Management - TELNET_ACCESS"),
    ("mpls.forwarding", "Mpls - FORWARDING"),
    ("mpls.l2vpn_circuit_cross_connect", "Mpls - L2VPN_CIRCUIT_CROSS_CONNECT"),
    ("mpls.l2vpn_point_to_multipoint", "Mpls - L2VPN_POINT_TO_MULTIPOINT"),
    ("mpls.l2vpn_point_to_point_vpws", "Mpls - L2VPN_POINT_TO_POINT_VPWS"),
    ("mpls.l2vpn_pseudowires", "Mpls - L2VPN_PSEUDOWIRES"),
    ("mpls.l3vpn_pe_routers", "Mpls - L3VPN_PE_ROUTERS"),
    ("mpls.l3vpn_pe_vrfs", "Mpls - L3VPN_PE_VRFS"),
    ("mpls.l3vpn_vrf_targets", "Mpls - L3VPN_VRF_TARGETS"),
    ("mpls.ldp_interfaces", "Mpls - LDP_INTERFACES"),
    ("mpls.ldp_neighbors", "Mpls - LDP_NEIGHBORS"),
    ("mpls.rsvp_interfaces", "Mpls - RSVP_INTERFACES"),
    ("mpls.rsvp_neighbors", "Mpls - RSVP_NEIGHBORS"),
    ("multicast.igmp_groups", "Multicast - IGMP_GROUPS"),
    ("multicast.igmp_interfaces", "Multicast - IGMP_INTERFACES"),
    (
        "multicast.igmp_snooping_global_config",
        "Multicast - IGMP_SNOOPING_GLOBAL_CONFIG",
    ),
    ("multicast.igmp_snooping_groups", "Multicast - IGMP_SNOOPING_GROUPS"),
    ("multicast.igmp_snooping_vlans", "Multicast - IGMP_SNOOPING_VLANS"),
    ("multicast.mac_table", "Multicast - MAC_TABLE"),
    ("multicast.mroute_counters", "Multicast - MROUTE_COUNTERS"),
    ("multicast.mroute_first_hop_router", "Multicast - MROUTE_FIRST_HOP_ROUTER"),
    ("multicast.mroute_oil_detail", "Multicast - MROUTE_OIL_DETAIL"),
    ("multicast.mroute_overview", "Multicast - MROUTE_OVERVIEW"),
    ("multicast.mroute_sources", "Multicast - MROUTE_SOURCES"),
    ("multicast.mroute_table", "Multicast - MROUTE_TABLE"),
    ("multicast.pim_interfaces", "Multicast - PIM_INTERFACES"),
    ("multicast.pim_neighbors", "Multicast - PIM_NEIGHBORS"),
    ("multicast.rp_bsr", "Multicast - RP_BSR"),
    ("multicast.rp_mappings", "Multicast - RP_MAPPINGS"),
    ("multicast.rp_mappings_groups", "Multicast - RP_MAPPINGS_GROUPS"),
    (
        "oam.unidirectional_link_detection_interfaces",
        "Oam - UNIDIRECTIONAL_LINK_DETECTION_INTERFACES",
    ),
    (
        "oam.unidirectional_link_detection_neighbors",
        "Oam - UNIDIRECTIONAL_LINK_DETECTION_NEIGHBORS",
    ),
    (
        "platforms.cisco_fabric_path_isis_neighbors",
        "Platforms - CISCO_FABRIC_PATH_ISIS_NEIGHBORS",
    ),
    ("platforms.cisco_fabric_path_routes", "Platforms - CISCO_FABRIC_PATH_ROUTES"),
    ("platforms.cisco_fabric_path_summary", "Platforms - CISCO_FABRIC_PATH_SUMMARY"),
    ("platforms.cisco_fabric_path_switches", "Platforms - CISCO_FABRIC_PATH_SWITCHES"),
    ("platforms.cisco_fex_interfaces", "Platforms - CISCO_FEX_INTERFACES"),
    ("platforms.cisco_fex_modules", "Platforms - CISCO_FEX_MODULES"),
    ("platforms.cisco_vdc_devices", "Platforms - CISCO_VDC_DEVICES"),
    ("platforms.cisco_vss_chassis", "Platforms - CISCO_VSS_CHASSIS"),
    ("platforms.cisco_vss_vsl", "Platforms - CISCO_VSS_VSL"),
    ("platforms.environment_fans", "Platforms - ENVIRONMENT_FANS"),
    ("platforms.environment_modules", "Platforms - ENVIRONMENT_MODULES"),
    ("platforms.environment_power_supplies", "Platforms - ENVIRONMENT_POWER_SUPPLIES"),
    (
        "platforms.environment_power_supplies_fans",
        "Platforms - ENVIRONMENT_POWER_SUPPLIES_FANS",
    ),
    (
        "platforms.environment_temperature_sensors",
        "Platforms - ENVIRONMENT_TEMPERATURE_SENSORS",
    ),
    ("platforms.juniper_cluster", "Platforms - JUNIPER_CLUSTER"),
    ("platforms.logical_devices", "Platforms - LOGICAL_DEVICES"),
    ("platforms.platform_cisco_vss", "Platforms - PLATFORM_CISCO_VSS"),
    ("platforms.poe_devices", "Platforms - POE_DEVICES"),
    ("platforms.poe_interfaces", "Platforms - POE_INTERFACES"),
    ("platforms.poe_modules", "Platforms - POE_MODULES"),
    ("platforms.stacks", "Platforms - STACKS"),
    ("platforms.stacks_members", "Platforms - STACKS_MEMBERS"),
    ("platforms.stacks_stack_ports", "Platforms - STACKS_STACK_PORTS"),
    (
        "port_channels.inbound_balancing_table",
        "Port_channels - INBOUND_BALANCING_TABLE",
    ),
    ("port_channels.member_status_table", "Port_channels - MEMBER_STATUS_TABLE"),
    ("port_channels.mlag_cisco_vpc", "Port_channels - MLAG_CISCO_VPC"),
    ("port_channels.mlag_peers", "Port_channels - MLAG_PEERS"),
    ("port_channels.mlag_switches", "Port_channels - MLAG_SWITCHES"),
    (
        "port_channels.outbound_balancing_table",
        "Port_channels - OUTBOUND_BALANCING_TABLE",
    ),
    ("qos.marking", "Qos - MARKING"),
    ("qos.policing", "Qos - POLICING"),
    ("qos.policy_maps", "Qos - POLICY_MAPS"),
    ("qos.priority_queuing", "Qos - PRIORITY_QUEUING"),
    ("qos.queuing", "Qos - QUEUING"),
    ("qos.random_drops", "Qos - RANDOM_DROPS"),
    ("qos.shaping", "Qos - SHAPING"),
    ("routing.bgp_address_families", "Routing - BGP_ADDRESS_FAMILIES"),
    ("routing.bgp_neighbors", "Routing - BGP_NEIGHBORS"),
    ("routing.eigrp_interfaces", "Routing - EIGRP_INTERFACES"),
    ("routing.eigrp_neighbors", "Routing - EIGRP_NEIGHBORS"),
    ("routing.isis_interfaces", "Routing - ISIS_INTERFACES"),
    ("routing.isis_levels", "Routing - ISIS_LEVELS"),
    ("routing.isis_neighbors", "Routing - ISIS_NEIGHBORS"),
    ("routing.lisp_map_resolvers_ipv4", "Routing - LISP_MAP_RESOLVERS_IPV4"),
    ("routing.lisp_map_resolvers_ipv6", "Routing - LISP_MAP_RESOLVERS_IPV6"),
    ("routing.lisp_routes_ipv4", "Routing - LISP_ROUTES_IPV4"),
    ("routing.lisp_routes_ipv6", "Routing - LISP_ROUTES_IPV6"),
    ("routing.ospf_interfaces", "Routing - OSPF_INTERFACES"),
    ("routing.ospf_neighbors", "Routing - OSPF_NEIGHBORS"),
    ("routing.ospfv3_interfaces", "Routing - OSPFV3_INTERFACES"),
    ("routing.ospfv3_neighbors", "Routing - OSPFV3_NEIGHBORS"),
    ("routing.policies", "Routing - POLICIES"),
    ("routing.policies_interfaces", "Routing - POLICIES_INTERFACES"),
    ("routing.policies_pbr", "Routing - POLICIES_PBR"),
    ("routing.policies_prefix_list", "Routing - POLICIES_PREFIX_LIST"),
    ("routing.policies_prefix_list_ipv6", "Routing - POLICIES_PREFIX_LIST_IPV6"),
    ("routing.rip_interfaces", "Routing - RIP_INTERFACES"),
    ("routing.rip_neighbors", "Routing - RIP_NEIGHBORS"),
    ("routing.routes_ipv4", "Routing - ROUTES_IPV4"),
    ("routing.routes_ipv6", "Routing - ROUTES_IPV6"),
    ("routing.summary_protocols", "Routing - SUMMARY_PROTOCOLS"),
    ("routing.summary_protocols_bgp", "Routing - SUMMARY_PROTOCOLS_BGP"),
    ("routing.summary_protocols_eigrp", "Routing - SUMMARY_PROTOCOLS_EIGRP"),
    ("routing.summary_protocols_isis", "Routing - SUMMARY_PROTOCOLS_ISIS"),
    ("routing.summary_protocols_ospf", "Routing - SUMMARY_PROTOCOLS_OSPF"),
    ("routing.summary_protocols_ospfv3", "Routing - SUMMARY_PROTOCOLS_OSPFV3"),
    ("routing.summary_protocols_rip", "Routing - SUMMARY_PROTOCOLS_RIP"),
    ("routing.vrf_detail", "Routing - VRF_DETAIL"),
    ("routing.vrf_interfaces", "Routing - VRF_INTERFACES"),
    ("sdn.aci_dtep", "Sdn - ACI_DTEP"),
    ("sdn.aci_endpoints", "Sdn - ACI_ENDPOINTS"),
    ("sdn.aci_vlan", "Sdn - ACI_VLAN"),
    ("sdn.aci_vrf", "Sdn - ACI_VRF"),
    ("sdn.apic_controllers", "Sdn - APIC_CONTROLLERS"),
    ("sdn.vxlan_interfaces", "Sdn - VXLAN_INTERFACES"),
    ("sdn.vxlan_peers", "Sdn - VXLAN_PEERS"),
    ("sdn.vxlan_vni", "Sdn - VXLAN_VNI"),
    ("sdn.vxlan_vtep", "Sdn - VXLAN_VTEP"),
    ("sdwan.links", "Sdwan - LINKS"),
    ("sdwan.sites", "Sdwan - SITES"),
    ("security.acl", "Security - ACL"),
    ("security.acl_global_policies", "Security - ACL_GLOBAL_POLICIES"),
    ("security.acl_interface", "Security - ACL_INTERFACE"),
    ("security.dhcp_snooping", "Security - DHCP_SNOOPING"),
    ("security.dhcp_snooping_bindings", "Security - DHCP_SNOOPING_BINDINGS"),
    ("security.dmvpn", "Security - DMVPN"),
    ("security.ipsec_gateways", "Security - IPSEC_GATEWAYS"),
    ("security.ipsec_tunnels", "Security - IPSEC_TUNNELS"),
    ("security.secure_ports_devices", "Security - SECURE_PORTS_DEVICES"),
    ("security.secure_ports_interfaces", "Security - SECURE_PORTS_INTERFACES"),
    ("security.secure_ports_users", "Security - SECURE_PORTS_USERS"),
    ("security.zone_firewall_interfaces", "Security - ZONE_FIREWALL_INTERFACES"),
    ("security.zone_firewall_policies", "Security - ZONE_FIREWALL_POLICIES"),
    ("technology.serial_ports", "Technology - SERIAL_PORTS"),
    ("stp.bridges", "Stp - BRIDGES"),
    ("stp.guards", "Stp - GUARDS"),
    ("stp.inconsistencies", "Stp - INCONSISTENCIES"),
    ("stp.inconsistencies_details", "Stp - INCONSISTENCIES_DETAILS"),
    (
        "stp.inconsistencies_stp_cdp_ports_mismatch",
        "Stp - INCONSISTENCIES_STP_CDP_PORTS_MISMATCH",
    ),
    ("stp.instances", "Stp - INSTANCES"),
    ("stp.neighbors", "Stp - NEIGHBORS"),
    ("stp.ports", "Stp - PORTS"),
    ("stp.vlans", "Stp - VLANS"),
    ("vlans.device_detail", "Vlans - DEVICE_DETAIL"),
    ("vlans.device_summary", "Vlans - DEVICE_SUMMARY"),
]


class IPFabricTableForm(BootstrapMixin, forms.Form):
    source = DynamicModelChoiceField(
        queryset=IPFabricSource.objects.all(),
        required=False,
        label=_("IP Fabric Source"),
    )
    snapshot_data = DynamicModelChoiceField(
        queryset=IPFabricSnapshot.objects.filter(status="loaded"),
        label=_("Snapshot"),
        required=False,
        query_params={
            "source_id": "$source",
            "status": "loaded",
        },
        help_text=_("IP Fabric snapshot to query. Defaults to $last if not specified."),
    )
    table = forms.ChoiceField(choices=tableChoices, required=True)
    cache_enable = forms.ChoiceField(
        choices=((True, "Yes"), (False, "No")),
        required=False,
        label=_("Cache"),
        initial=True,
        help_text=_("Cache results for 24 hours"),
    )
