import json as json_module

import django_tables2 as tables
from django.utils.html import escape, format_html
from django.utils.safestring import mark_safe
from netbox.tables.columns import BooleanColumn, ToggleColumn
from utilities.paginator import EnhancedPaginator
from utilities.templatetags.helpers import humanize_speed

from netbox_librenms_plugin.models import InterfaceTypeMapping
from netbox_librenms_plugin.utils import (
    check_vlan_group_matches,
    convert_speed_to_kbps,
    format_mac_address,
    get_interface_name_field,
    get_librenms_device_id,
    get_missing_vlan_warning,
    get_table_paginate_count,
    get_tagged_vlan_css_class,
    get_untagged_vlan_css_class,
    get_virtual_chassis_member,
)


class LibreNMSInterfaceTable(tables.Table):
    """
    Table for displaying LibreNMS interface data.
    """

    class Meta:
        """Meta options for LibreNMSInterfaceTable."""

        sequence = [
            "selection",
            "name",
            "type",
            "speed",
            "vlans",
            "mac_address",
            "mtu",
            "enabled",
            "description",
            "librenms_id",
        ]
        attrs = {
            "class": "table table-hover object-list",
            "id": "librenms-interface-table",
        }

    def __init__(self, *args, device=None, interface_name_field=None, vlan_groups=None, server_key=None, **kwargs):
        """Initialize table with device context and interface name field."""
        self.device = device
        self.interface_name_field = interface_name_field or get_interface_name_field()
        self.vlan_groups = vlan_groups or []
        self.server_key = server_key

        # Update column accessors after initialization
        for column in ["selection", "name"]:
            self.base_columns[column].accessor = self.interface_name_field

        # Set row attributes using interface_name_field
        self._meta.row_attrs = {
            "data-interface": lambda record: record.get(self.interface_name_field),
            "data-name": lambda record: record.get(self.interface_name_field),
            "data-enabled": lambda record: (
                str(record.get("ifAdminStatus")).lower() if record.get("ifAdminStatus") is not None else ""
            ),
        }

        super().__init__(*args, **kwargs)
        self.tab = "interfaces"
        self.htmx_url = None
        self.prefix = "interfaces_"

    selection = ToggleColumn(
        orderable=False,
        visible=True,
        attrs={"td": {"data-col": "selection"}, "input": {"name": "select"}},
    )
    name = tables.Column(verbose_name="Name", attrs={"td": {"data-col": "name"}})
    type = tables.Column(
        accessor="ifType",
        verbose_name="Interface Type",
        attrs={"td": {"data-col": "type"}},
    )
    speed = tables.Column(accessor="ifSpeed", verbose_name="Speed", attrs={"td": {"data-col": "speed"}})
    mac_address = tables.Column(
        accessor="ifPhysAddress",
        verbose_name="MAC Address",
        attrs={"td": {"data-col": "mac_address"}},
    )
    mtu = tables.Column(accessor="ifMtu", verbose_name="MTU", attrs={"td": {"data-col": "mtu"}})
    enabled = BooleanColumn(verbose_name="Enabled", attrs={"td": {"data-col": "enabled"}})
    description = tables.Column(
        accessor="ifAlias",
        verbose_name="Description",
        attrs={"td": {"data-col": "description"}},
    )
    librenms_id = tables.Column(
        accessor="port_id",
        verbose_name="LibreNMS ID",
        attrs={"td": {"data-col": "librenms_id"}},
    )
    vlans = tables.Column(
        verbose_name="VLANs",
        empty_values=(),
        orderable=False,
        attrs={"td": {"data-col": "vlans"}},
    )

    def render_vlans(self, value, record):
        """
        Render VLANs column showing untagged and tagged VLANs.
        Format: "100(U), 200(T), 300(T)" or "100(U)" for access ports.

        Color logic:
        - Red + warning icon: VLAN not in any NetBox group (cannot sync)
        - Red: Not present in NetBox (no VLAN assigned on interface)
        - Orange: Mismatched (different untagged VLAN assigned)
        - Green: Matching (VLAN matches NetBox assignment)

        Compact display: shows up to 3 VLANs inline, then summarizes.
        An edit button opens the VLAN detail modal.
        Hidden inputs store per-VLAN group assignments for form submission.
        """
        untagged = record.get("untagged_vlan")
        tagged = record.get("tagged_vlans", [])
        missing_vlans = record.get("missing_vlans", [])

        # Get NetBox interface for comparison
        exists_in_netbox = record.get("exists_in_netbox", False)
        netbox_interface = record.get("netbox_interface")

        # Get NetBox VLAN assignments (VID + group for group-aware comparison)
        netbox_untagged_vid = None
        netbox_untagged_group_id = None
        netbox_tagged_vids = set()
        netbox_tagged_group_ids = {}
        if netbox_interface:
            if netbox_interface.untagged_vlan:
                netbox_untagged_vid = netbox_interface.untagged_vlan.vid
                netbox_untagged_group_id = netbox_interface.untagged_vlan.group_id
            for v in netbox_interface.tagged_vlans.all():
                netbox_tagged_vids.add(v.vid)
                netbox_tagged_group_ids[v.vid] = v.group_id

        all_vlans = []
        if untagged:
            all_vlans.append(("U", untagged))
        for vid in sorted(tagged):
            all_vlans.append(("T", vid))

        if not all_vlans:
            return mark_safe("—")

        interface_name = record.get(self.interface_name_field, "")
        safe_name = interface_name.replace("/", "_").replace(":", "_")

        # Build compact colored summary (show up to 3 VLANs, summarize rest)
        vlan_group_map = record.get("vlan_group_map", {})
        MAX_INLINE = 3
        inline_parts = []
        for vlan_type, vid in all_vlans[:MAX_INLINE]:
            selected_gid = self._parse_group_id(vlan_group_map.get(vid, {}).get("group_id", ""))
            group_matches = check_vlan_group_matches(
                vlan_type,
                vid,
                selected_gid,
                netbox_untagged_group_id,
                netbox_tagged_group_ids,
                netbox_untagged_vid,
                netbox_tagged_vids,
            )
            if vlan_type == "U":
                css = get_untagged_vlan_css_class(
                    vid, netbox_untagged_vid, exists_in_netbox, missing_vlans, group_matches
                )
            else:
                css = get_tagged_vlan_css_class(vid, netbox_tagged_vids, exists_in_netbox, missing_vlans, group_matches)
            warning = get_missing_vlan_warning(vid, missing_vlans)
            inline_parts.append(f'<span class="{css}">{vid}({vlan_type}){warning}</span>')

        summary = ", ".join(inline_parts)
        if len(all_vlans) > MAX_INLINE:
            extra = len(all_vlans) - MAX_INLINE
            summary += f' <span class="text-muted">+{extra} more</span>'

        # Build tooltip showing auto-selected VLAN group per VLAN
        tooltip_lines = []
        for vlan_type, vid in all_vlans:
            if vid in missing_vlans:
                tooltip_lines.append(f"VLAN {vid}({vlan_type}) → ⚠ Not in NetBox")
            else:
                group_info = vlan_group_map.get(vid, {})
                group_name = group_info.get("group_name", "Global")
                tooltip_lines.append(f"VLAN {vid}({vlan_type}) → {escape(group_name)}")
        tooltip_text = "&#10;".join(tooltip_lines)

        # Build hidden inputs for per-VLAN group selections (submitted with form)
        hidden_inputs = []
        for vlan_type, vid in all_vlans:
            group_info = vlan_group_map.get(vid, {})
            group_id = group_info.get("group_id", "")
            hidden_inputs.append(
                format_html(
                    '<input type="hidden" name="vlan_group_{}_{}" '
                    'value="{}" class="vlan-group-hidden" '
                    'data-interface="{}" data-vid="{}">',
                    safe_name,
                    vid,
                    group_id,
                    interface_name,
                    vid,
                )
            )

        # Build JSON data for modal (use proper json serialization for safety)
        vlan_json_items = []
        for vlan_type, vid in all_vlans:
            group_info = vlan_group_map.get(vid, {})
            is_missing = vid in missing_vlans
            selected_gid = self._parse_group_id(group_info.get("group_id", ""))
            group_matches = check_vlan_group_matches(
                vlan_type,
                vid,
                selected_gid,
                netbox_untagged_group_id,
                netbox_tagged_group_ids,
                netbox_untagged_vid,
                netbox_tagged_vids,
            )
            if vlan_type == "U":
                css = get_untagged_vlan_css_class(
                    vid, netbox_untagged_vid, exists_in_netbox, missing_vlans, group_matches
                )
            else:
                css = get_tagged_vlan_css_class(vid, netbox_tagged_vids, exists_in_netbox, missing_vlans, group_matches)
            display_group_name = "Not in NetBox" if is_missing else group_info.get("group_name", "Global")
            vlan_json_items.append(
                {
                    "vid": vid,
                    "type": vlan_type,
                    "group_id": group_info.get("group_id", ""),
                    "group_name": display_group_name,
                    "css": css,
                    "missing": is_missing,
                }
            )
        vlan_json = json_module.dumps(vlan_json_items)

        device_id = self.device.pk if self.device else ""

        # Build vlan_groups JSON for modal dropdowns
        group_options = [{"id": "", "name": "-- No Group (Global) --", "scope": ""}]
        for group in self.vlan_groups:
            scope_info = str(group.scope) if hasattr(group, "scope") and group.scope else ""
            group_options.append({"id": str(group.pk), "name": group.name, "scope": scope_info})

        groups_json = json_module.dumps(group_options)

        # Escape JSON for safe embedding in HTML attributes
        escaped_vlan_json = escape(vlan_json)
        escaped_groups_json = escape(groups_json)

        edit_btn = format_html(
            '<button type="button" class="btn btn-sm btn-link p-0 ms-1 vlan-edit-btn" '
            'data-interface="{}" '
            'data-safe-name="{}" '
            'data-device-id="{}" '
            "data-vlans='{}' "
            "data-vlan-groups='{}' "
            'title="Edit VLAN group assignments">'
            '<i class="mdi mdi-pencil"></i></button>',
            interface_name,
            safe_name,
            device_id,
            escaped_vlan_json,
            escaped_groups_json,
        )

        hidden_inputs_html = mark_safe("".join(str(h) for h in hidden_inputs))

        return format_html(
            '<span title="{}">{}</span>{}{}',
            mark_safe(tooltip_text),
            mark_safe(summary),
            edit_btn,
            hidden_inputs_html,
        )

    @staticmethod
    def _parse_group_id(group_id_str):
        """Normalize a group ID string to int or None for comparison."""
        return int(group_id_str) if group_id_str else None

    def render_speed(self, value, record):
        """Render interface speed with appropriate styling based on comparison with NetBox"""
        kbps_value = convert_speed_to_kbps(value)
        return self._render_field(humanize_speed(kbps_value), record, "ifSpeed", "speed")

    def render_name(self, value, record):
        """Render interface name with appropriate styling based on comparison with NetBox"""
        return self._render_field(value, record, self.interface_name_field, "name")

    def _get_interface_status_display(self, enabled, record):
        """
        Determine interface status display and CSS class based on enabled state and NetBox comparison.

        Args:
            enabled (bool): Interface enabled state.
            record (dict): Interface data record.

        Returns:
            tuple: (display_value, css_class)
        """
        display_value = "Enabled" if enabled else "Disabled"

        if not record.get("exists_in_netbox"):
            return display_value, "text-danger"

        netbox_interface = record.get("netbox_interface")
        if netbox_interface:
            netbox_enabled = netbox_interface.enabled
            if enabled == netbox_enabled:
                return display_value, "text-success"
            return display_value, "text-warning"

        return display_value, "text-danger"

    def _parse_enabled_status(self, value):
        """Convert interface status value to boolean enabled state"""
        if isinstance(value, str):
            return value.lower() == "up"
        return bool(value)

    def render_enabled(self, value, record):
        """Render interface enabled status with appropriate styling based on comparison with NetBox"""
        enabled = self._parse_enabled_status(value)
        display_value, css_class = self._get_interface_status_display(enabled, record)
        return format_html('<span class="{}">{}</span>', css_class, display_value)

    def render_description(self, value, record):
        """Render interface description with appropriate styling based on comparison with NetBox"""
        return self._render_field(value, record, "ifAlias", "description")

    def render_mac_address(self, value, record):
        """Render MAC address with appropriate styling based on comparison with NetBox"""
        formatted_mac = format_mac_address(value)
        return self._render_field(formatted_mac, record, "ifPhysAddress", "mac_address")

    def render_mtu(self, value, record):
        """Render MTU with appropriate styling based on comparison with NetBox"""
        return self._render_field(value, record, "ifMtu", "mtu")

    def render_librenms_id(self, value, record):
        """Render the 'librenms_id' field with appropriate styling based on comparison with NetBox."""

        if not record.get("exists_in_netbox"):
            return mark_safe(f'<span class="text-danger">{value}</span>')

        netbox_interface = record.get("netbox_interface")
        if not netbox_interface:
            return mark_safe(f'<span class="text-danger">{value}</span>')

        netbox_librenms_id = get_librenms_device_id(netbox_interface, self.server_key, auto_save=False)

        if netbox_librenms_id is None:
            return mark_safe(
                f'<span class="text-danger" title="No librenms_id custom field value found">{value}</span>'
            )

        # Compare the IDs
        if str(value) != str(netbox_librenms_id):
            # IDs do not match
            return mark_safe(
                f'<span class="text-warning" title="Existing LibreNMS ID: {netbox_librenms_id}">{value}</span>'
            )
        else:
            # IDs match
            return mark_safe(f'<span class="text-success">{value}</span>')

    def _compare_mac_addresses(self, librenms_mac, netbox_interface):
        """
        Compare LibreNMS MAC address against all MAC addresses on NetBox interface.

        Args:
            librenms_mac (str): MAC address from LibreNMS.
            netbox_interface (Interface): NetBox interface record.

        Returns:
            True if MAC exists on interface.
        """
        if not netbox_interface:
            return False

        interface_macs = [mac.mac_address for mac in netbox_interface.mac_addresses.all()]
        return librenms_mac in interface_macs

    def _render_field(self, value, record, librenms_key, netbox_key):
        """Render a field value with appropriate styling based on the comparison with NetBox."""

        if not record.get("exists_in_netbox"):
            return mark_safe(f'<span class="text-danger">{value}</span>')

        netbox_interface = record.get("netbox_interface")
        if not netbox_interface:
            return mark_safe(f'<span class="text-danger">{value}</span>')

        if librenms_key == "ifPhysAddress":
            mac_matches = self._compare_mac_addresses(value, netbox_interface)
            css_class = "text-success" if mac_matches else "text-warning"
            return mark_safe(f'<span class="{css_class}">{value}</span>')

        netbox_value = getattr(netbox_interface, netbox_key, None)
        librenms_value = record.get(librenms_key)

        if librenms_key == "ifSpeed":
            librenms_value = convert_speed_to_kbps(librenms_value)

        if librenms_value != netbox_value:
            return mark_safe(f'<span class="text-warning">{value}</span>')

        return mark_safe(f'<span class="text-success">{value}</span>')

    def render_type(self, value, record):
        """Render interface type with appropriate styling based on comparison with NetBox"""
        speed = convert_speed_to_kbps(record.get("ifSpeed", 0))
        mapping = self.get_interface_mapping(value, speed)
        tooltip_value, icon = self.render_mapping_tooltip(value, speed, mapping)

        combined_display = format_html("{} {}", tooltip_value, icon)

        if not record.get("exists_in_netbox"):
            return format_html('<span class="text-danger">{}</span>', combined_display)

        netbox_interface = record.get("netbox_interface")

        if netbox_interface:
            netbox_type = getattr(netbox_interface, "type", None)
            if mapping and mapping.netbox_type == netbox_type:
                return format_html('<span class="text-success">{}</span>', combined_display)
            elif mapping:
                return format_html('<span class="text-warning">{}</span>', combined_display)

        return format_html('<span class="text-danger">{}</span>', combined_display)

    def get_interface_mapping(self, librenms_type, speed):
        """Get interface type mapping based on type and speed"""

        # First try exact match with type and speed
        mapping = InterfaceTypeMapping.objects.filter(librenms_type=librenms_type, librenms_speed=speed).first()

        # If no match found, fall back to type-only match
        if not mapping:
            mapping = InterfaceTypeMapping.objects.filter(
                librenms_type=librenms_type, librenms_speed__isnull=True
            ).first()

        return mapping

    def render_mapping_tooltip(self, value, speed, mapping):
        """Render tooltip for interface type mapping"""
        if mapping:
            display = mapping.netbox_type
            icon = format_html(
                '<i class="mdi mdi-link-variant" title="Mapped from LibreNMS type: {} (Speed: {})"></i>',
                value,
                speed,
            )
        else:
            display = value
            icon = mark_safe('<i class="mdi mdi-link-variant-off" title="No mapping to NetBox type"></i>')
        return display, icon

    def format_interface_data(self, port_data, device):
        """Format single interface data using table rendering logic"""

        # Add NetBox interface data
        interface_name = port_data.get(self.interface_name_field)

        port_data["netbox_interface"] = device.interfaces.filter(name=interface_name).first()
        port_data["exists_in_netbox"] = bool(port_data["netbox_interface"])

        # Clear description if it matches interface name
        if port_data["ifAlias"] == port_data["ifName"] or port_data["ifAlias"] == port_data["ifDescr"]:
            port_data["ifAlias"] = ""

        formatted_data = {
            "name": self.render_name(interface_name, port_data),
            "type": self.render_type(port_data["ifType"], port_data),
            "speed": self.render_speed(port_data["ifSpeed"], port_data),
            "mac_address": self.render_mac_address(port_data["ifPhysAddress"], port_data),
            "mtu": self.render_mtu(port_data["ifMtu"], port_data),
            "enabled": self.render_enabled(port_data["ifAdminStatus"], port_data),
            "description": self.render_description(port_data["ifAlias"], port_data),
        }

        return formatted_data

    def configure(self, request):
        """Configure the table with pagination and other options"""
        paginate = {
            "paginator_class": EnhancedPaginator,
            "per_page": get_table_paginate_count(request, self.prefix),
        }

        tables.RequestConfig(request, paginate).configure(self)


class VCInterfaceTable(LibreNMSInterfaceTable):
    """
    Table for displaying Virtual Chassis interface data.
    """

    device_selection = tables.Column(
        verbose_name="Virtual Chassis member",
        accessor="device",
        orderable=False,
        empty_values=[],
        attrs={"td": {"data-col": "device_selection"}},
    )

    def __init__(self, *args, device=None, interface_name_field=None, vlan_groups=None, **kwargs):
        """Initialize VC interface table with device and name field."""
        super().__init__(
            *args, device=device, interface_name_field=interface_name_field, vlan_groups=vlan_groups, **kwargs
        )
        # Ensure device_selection column is visible
        if hasattr(self.device, "virtual_chassis") and self.device.virtual_chassis:
            self.columns.show("device_selection")
            # Update selection column accessor to match interface_name_field
            self.base_columns["selection"].accessor = self.interface_name_field

    def render_device_selection(self, value, record):
        """
        Renders a device selection dropdown for virtual chassis members.
        Determines the selected member based on interface type and name.
        Returns an HTML select element with appropriate member options.
        """
        members = self.device.virtual_chassis.members.all()
        if_type = record.get("ifType", "").lower()
        interface_name = record.get(self.interface_name_field)

        if "ethernet" in if_type:
            chassis_member = get_virtual_chassis_member(self.device, interface_name)
            selected_member_id = chassis_member.id if chassis_member else self.device.id
        else:
            selected_member_id = self.device.id

        # Create unique base ID for TomSelect components
        base_id = f"device_selection_{interface_name}_{hash(interface_name)}"

        options = [
            f'<option value="{member.id}"{" selected" if member.id == selected_member_id else ""}>{member.name}</option>'
            for member in members
        ]

        return format_html(
            '<select name="device_selection_{0}" id="{1}" class="form-select vc-member-select" data-interface="{0}" data-row-id="{0}">{2}</select>',
            interface_name,
            base_id,
            mark_safe("".join(options)),
        )

    def format_interface_data(self, port_data, device):
        """Format interface data including VC device selection column."""
        formatted_data = super().format_interface_data(port_data, device)
        formatted_data["device_selection"] = self.render_device_selection(None, port_data)
        return formatted_data

    class Meta:
        """Meta options for VCInterfaceTable."""

        sequence = [
            "selection",
            "device_selection",
            "name",
            "type",
            "speed",
            "vlans",
            "mac_address",
            "mtu",
            "enabled",
            "description",
        ]
        attrs = {
            "class": "table table-hover object-list",
            "id": "librenms-interface-table",
        }


class LibreNMSVMInterfaceTable(LibreNMSInterfaceTable):
    """
    Table for displaying LibreNMS VM interface data.
    """

    class Meta(LibreNMSInterfaceTable.Meta):
        """Meta options for LibreNMSVMInterfaceTable."""

        sequence = [
            "selection",
            "name",
            "vlans",
            "mac_address",
            "mtu",
            "enabled",
            "description",
        ]
        attrs = {
            "class": "table table-hover object-list",
            "id": "librenms-interface-table-vm",
        }

    # Remove the type and speed column for VMs
    type = None
    speed = None
