import json

import django_tables2 as tables
from dcim.models import Device
from dcim.tables import DeviceTable
from django.urls import reverse
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django_tables2 import Column
from virtualization.models import VirtualMachine

from netbox_librenms_plugin.utils import get_librenms_sync_device


class DeviceStatusTable(DeviceTable):
    """
    Table for displaying device LibreNMS status.
    """

    librenms_status = Column(
        verbose_name="LibreNMS Status",
        empty_values=(),
        accessor="librenms_status",
        orderable=False,
    )

    def render_librenms_status(self, value, record):
        """Render LibreNMS sync status with link to sync page."""
        sync_url = reverse(
            "plugins:netbox_librenms_plugin:device_librenms_sync",
            kwargs={"pk": record.pk},
        )

        # Check if device is VC member and redirect to sync device if different
        if hasattr(record, "virtual_chassis") and record.virtual_chassis:
            sync_device = get_librenms_sync_device(record)
            if sync_device and record.pk != sync_device.pk:
                sync_device_url = reverse(
                    "plugins:netbox_librenms_plugin:device_librenms_sync",
                    kwargs={"pk": sync_device.pk},
                )
                return mark_safe(
                    f'<a href="{sync_device_url}"><span class="text-info">'
                    f'<i class="mdi mdi-server-network"></i> See {sync_device.name}</span></a>'
                )
        if value:
            status = '<span class="text-success"><i class="mdi mdi-check-circle"></i> Found</span>'
        elif value is False:
            status = '<span class="text-danger"><i class="mdi mdi-close-circle"></i> Not Found</span>'
        else:
            status = '<span class="text-secondary"><i class="mdi mdi-help-circle"></i> Unknown</span>'

        return mark_safe(f'<a href="{sync_url}">{status}</a>')

    class Meta(DeviceTable.Meta):
        """Meta options for DeviceStatusTable."""

        model = Device
        fields = (
            "pk",
            "name",
            "status",
            "tenant",
            "site",
            "location",
            "rack",
            "role",
            "manufacturer",
            "device_type",
            "device_role",
            "librenms_status",
        )
        default_columns = (
            "name",
            "status",
            "site",
            "location",
            "rack",
            "device_type",
            "role",
            "librenms_status",
        )


class DeviceImportTable(tables.Table):
    """
    Table for displaying LibreNMS devices available for import.
    Shows validation status and provides import actions.
    Uses plain django_tables2.Table since we're working with dictionaries, not model instances.
    """

    name = "DeviceImportTable"  # Required by NetBox table utilities

    def __init__(self, *args, **kwargs):
        """Initialize table with cached querysets and apply sorting."""
        super().__init__(*args, **kwargs)

        # Cache querysets to avoid N queries per render
        from dcim.models import DeviceRole
        from virtualization.models import Cluster

        self._cached_clusters = list(Cluster.objects.all().order_by("name"))
        self._cached_roles = list(DeviceRole.objects.all().order_by("name"))

        # Apply sorting if order_by is specified
        # Since we're working with dictionaries, not QuerySets, we handle sorting manually
        if self.order_by:
            self._sort_data()

    def _sort_data(self):
        """Sort table data based on order_by parameter."""
        if not self.data:
            return

        # Get the ordering field and direction
        order_by = self.order_by[0] if isinstance(self.order_by, (list, tuple)) else self.order_by
        reverse = order_by.startswith("-")
        field = order_by.lstrip("-")

        # Map column names to data keys
        field_map = {
            "hostname": "hostname",
            "sysname": "sysName",
            "location": "location",
            "hardware": "hardware",
        }

        data_key = field_map.get(field)
        if not data_key:
            return  # Unknown field, skip sorting

        # Sort the data list in place
        # Handle None values by treating them as empty strings for sorting
        def sort_key(item):
            """Return lowercase sort value for a data field."""
            value = item.get(data_key, "")
            return (value or "").lower() if isinstance(value, str) else str(value or "")

        try:
            self.data.data.sort(key=sort_key, reverse=reverse)
        except (AttributeError, TypeError):
            # If data is a plain list, sort it directly
            if isinstance(self.data, list):
                self.data.sort(key=sort_key, reverse=reverse)

    # Selection checkbox
    selection = Column(
        verbose_name="",
        empty_values=(),
        orderable=False,
        accessor="device_id",
    )

    # LibreNMS device fields
    hostname = Column(verbose_name="Hostname", accessor="hostname", orderable=True)
    sysname = Column(verbose_name="System Name", accessor="sysName", orderable=True)
    location = Column(verbose_name="Location", accessor="location", orderable=True)
    hardware = Column(verbose_name="Hardware", accessor="hardware", orderable=True)

    # Cluster selection - if selected, import as VM; otherwise import as Device
    netbox_cluster = Column(
        verbose_name="NetBox Cluster",
        empty_values=(),
        orderable=False,
        accessor="device_id",
    )

    # NetBox role selection (for devices only)
    netbox_role = Column(
        verbose_name="NetBox Role",
        empty_values=(),
        orderable=False,
        accessor="device_id",
    )

    # NetBox rack selection (for devices only, optional)
    netbox_rack = Column(
        verbose_name="NetBox Rack",
        empty_values=(),
        orderable=False,
        accessor="device_id",
    )

    # Virtual Chassis detection column
    virtual_chassis = Column(
        verbose_name="Virtual Chassis",
        empty_values=(),
        orderable=False,
        accessor="device_id",
    )

    # Actions column
    actions = Column(
        verbose_name="Actions",
        empty_values=(),
        orderable=False,
        accessor="device_id",
    )

    def render_selection(self, value, record):
        """
        Render selection checkbox.
        Disabled if device can't be imported.
        """
        validation = record.get("_validation", {})
        can_import = validation.get("can_import", False)
        device_id = record.get("device_id")
        hostname = record.get("hostname", "")
        sysname = record.get("sysName", "")

        if can_import:
            return mark_safe(
                f'<input type="checkbox" name="select" value="{device_id}" '
                f'class="form-check-input device-select" data-device-id="{device_id}" '
                f'data-hostname="{hostname}" data-sysname="{sysname}">'
            )
        else:
            return mark_safe(
                '<input type="checkbox" disabled class="form-check-input" title="Cannot import this device">'
            )

    def render_hostname(self, value, record):
        """Render hostname with link to LibreNMS if available."""
        return mark_safe(f"<strong>{value}</strong>")

    def render_netbox_cluster(self, value, record):
        """
        Render cluster selection dropdown.
        Default is "-- Device (not VM) --" (empty value).
        If a cluster is selected, the device will be imported as a VM.
        If no cluster is selected, the device will be imported as a Device.
        """
        device_id = record.get("device_id")
        validation = record.get("_validation", {})
        existing = validation.get("existing_device")

        # Check if existing object is a VM
        if existing and isinstance(existing, VirtualMachine):
            # VM already exists - show its cluster (cluster is required for VMs)
            cluster = existing.cluster
            return mark_safe(f'<span class="badge bg-info text-white">{cluster.name}</span>')

        # If Device already exists (not VM), show it's not a VM
        if existing:
            return mark_safe('<span class="text-muted small">Device (not VM)</span>')

        # Use cached clusters to avoid N queries
        clusters = self._cached_clusters

        # Check if a cluster has been selected (from validation)
        selected_cluster_id = None
        if validation.get("cluster", {}).get("found") and validation.get("cluster", {}).get("cluster"):
            selected_cluster_id = validation["cluster"]["cluster"].pk

        # Build dropdown with HTMX attributes to update the row
        options = ['<option value="">-- Device (not VM) --</option>']
        for cluster in clusters:
            selected = " selected" if cluster.pk == selected_cluster_id else ""
            options.append(f'<option value="{cluster.pk}"{selected}>{cluster.name}</option>')

        # Add HTMX attributes to update the entire row when cluster is selected
        from django.urls import reverse

        update_url = reverse(
            "plugins:netbox_librenms_plugin:device_cluster_update",
            kwargs={"device_id": device_id},
        )

        # Include VC detection flag in URL if present in validation (from initial load)
        vc_detection_flag = ""
        if validation.get("_vc_detection_enabled"):
            vc_detection_flag = "?enable_vc_detection=true"

        select_html = (
            f'<select class="form-select form-select-sm cluster-select" '
            f'name="cluster_{device_id}" '
            f'data-device-id="{device_id}" '
            f'hx-post="{update_url}{vc_detection_flag}" '
            f'hx-trigger="change" '
            f'hx-swap="none" '
            f'hx-include="[name=role_{device_id}], [name=rack_{device_id}]" '
            f'style="width: 180px;">'
            f"{''.join(options)}"
            f"</select>"
        )

        return mark_safe(select_html)

    def render_netbox_role(self, value, record):
        """
        Render role selection dropdown.
        For Devices: Role is required
        For VMs: Role is optional
        """
        device_id = record.get("device_id")
        validation = record.get("_validation", {})
        is_vm = validation.get("import_as_vm", False)
        existing = validation.get("existing_device")

        # If device/VM already exists, show its role with NetBox's defined color
        if existing and hasattr(existing, "role") and existing.role:
            role = existing.role
            # Use the role's color if available, otherwise fallback to info
            color = role.color if hasattr(role, "color") and role.color else "6c757d"
            return mark_safe(
                f'<span class="badge" style="background-color: #{color}; color: white;">{role.name}</span>'
            )

        # Use cached roles to avoid N queries
        roles = self._cached_roles

        # Check if a role has been selected (from validation)
        selected_role_id = None
        if validation.get("device_role", {}).get("found") and validation.get("device_role", {}).get("role"):
            selected_role_id = validation["device_role"]["role"].pk

        # Build dropdown with different text based on import type
        if is_vm:
            placeholder = "-- Select Role (Optional) --"
        else:
            placeholder = "-- Select Role --"

        options = [f'<option value="">{placeholder}</option>']
        for role in roles:
            selected = " selected" if role.pk == selected_role_id else ""
            options.append(f'<option value="{role.pk}"{selected}>{role.name}</option>')

        # Add HTMX attributes to update the entire row when role is selected
        from django.urls import reverse

        update_url = reverse(
            "plugins:netbox_librenms_plugin:device_role_update",
            kwargs={"device_id": device_id},
        )

        # Include VC detection flag in URL if present in validation (from initial load)
        vc_detection_flag = ""
        if validation.get("_vc_detection_enabled"):
            vc_detection_flag = "?enable_vc_detection=true"

        select_html = (
            f'<select class="form-select form-select-sm device-role-select" '
            f'name="role_{device_id}" '
            f'data-device-id="{device_id}" '
            f'hx-post="{update_url}{vc_detection_flag}" '
            f'hx-trigger="change" '
            f'hx-swap="none" '
            f'hx-include="[name=cluster_{device_id}], [name=rack_{device_id}]" '
            f'style="width: 150px;">'
            f"{''.join(options)}"
            f"</select>"
        )

        return mark_safe(select_html)

    def render_netbox_rack(self, value, record):
        """
        Render rack selection dropdown (optional).
        Shows racks for the matched site in "Location - Rack" format.
        Only shown for devices (not VMs) and when site is matched.
        """
        device_id = record.get("device_id")
        validation = record.get("_validation", {})
        is_vm = validation.get("import_as_vm", False)
        existing = validation.get("existing_device")

        # Don't show rack dropdown for VMs
        if is_vm:
            return mark_safe('<span class="text-muted small">N/A (VM)</span>')

        # If device already exists, show its rack
        if existing and hasattr(existing, "rack") and existing.rack:
            rack = existing.rack
            location_name = rack.location.name if rack.location else "No Location"
            return mark_safe(f'<span class="badge bg-info text-white">{location_name} - {rack.name}</span>')

        # If device exists but no rack assigned
        if existing:
            return mark_safe('<span class="text-muted small">No rack</span>')

        # Check if site is matched - rack selection only available when site is known
        site_found = validation.get("site", {}).get("found", False)
        if not site_found:
            return mark_safe('<span class="text-muted small">--</span>')

        # Get available racks from validation (cached)
        available_racks = validation.get("rack", {}).get("available_racks", [])

        # Check if a rack has been selected
        selected_rack_id = None
        if validation.get("rack", {}).get("rack"):
            selected_rack_id = validation["rack"]["rack"].pk

        # Build dropdown with HTMX attributes
        options = ['<option value="">--</option>']
        for rack in available_racks:
            location_name = rack.location.name if rack.location else "No Location"
            display_text = f"{location_name} - {rack.name}"
            selected = " selected" if rack.pk == selected_rack_id else ""
            options.append(f'<option value="{rack.pk}"{selected}>{escape(display_text)}</option>')

        # Add HTMX attributes to update the entire row when rack is selected
        from django.urls import reverse

        update_url = reverse(
            "plugins:netbox_librenms_plugin:device_rack_update",
            kwargs={"device_id": device_id},
        )

        # Include VC detection flag in URL if present in validation (from initial load)
        vc_detection_flag = ""
        if validation.get("_vc_detection_enabled"):
            vc_detection_flag = "?enable_vc_detection=true"

        select_html = (
            f'<select class="form-select form-select-sm rack-select" '
            f'name="rack_{device_id}" '
            f'data-device-id="{device_id}" '
            f'hx-post="{update_url}{vc_detection_flag}" '
            f'hx-trigger="change" '
            f'hx-swap="none" '
            f'hx-include="[name=cluster_{device_id}], [name=role_{device_id}]" '
            f'style="width: 200px;">'
            f"{''.join(options)}"
            f"</select>"
        )

        return mark_safe(select_html)

    def render_actions(self, value, record):
        """
        Render action buttons for import using HTMX.
        Shows Import button if can import, otherwise shows Preview/Configure.
        Permission checks are handled by backend require_write_permission() which shows toast.
        """
        validation = record.get("_validation", {})
        device_id = record.get("device_id")
        is_ready = validation.get("is_ready", False)
        can_import = validation.get("can_import", False)
        existing = validation.get("existing_device")

        vc_attributes = self._build_vc_attributes(validation, record)

        buttons = []

        if existing:
            # Link to existing device/VM in NetBox + details button for conflict resolution
            if isinstance(existing, VirtualMachine):
                url_name = "virtualization:virtualmachine"
                title = "View VM in NetBox"
            else:
                url_name = "dcim:device"
                title = "View Device in NetBox"

            device_url = reverse(url_name, kwargs={"pk": existing.pk})
            buttons.append(
                f'<a href="{device_url}" class="btn btn-sm btn-secondary" '
                f'title="{title}" aria-label="{title}"><i class="mdi mdi-open-in-new"></i></a>'
            )

            # Add details/conflict button for conflict resolution actions
            details_url = self._build_validation_details_url(device_id, validation)
            match_type = validation.get("existing_match_type", "")
            serial_action = validation.get("serial_action")
            has_mismatch = validation.get("device_type_mismatch", False)
            has_actions = match_type == "hostname" or (match_type == "serial" and serial_action is not None)
            has_name_sync = validation.get("name_sync_available", False)
            has_sync_needed = match_type == "librenms_id" and serial_action in ("update_serial", "conflict")

            if has_mismatch:
                btn_class = "btn-outline-danger"
                btn_icon = "mdi-alert-circle"
                btn_label = " Conflict"
                btn_title = "View conflict details"
            elif has_actions:
                btn_class = "btn-outline-warning"
                btn_icon = "mdi-alert"
                btn_label = " Conflict"
                btn_title = "View conflict details"
            elif has_name_sync or has_sync_needed:
                btn_class = "btn-outline-warning"
                btn_icon = "mdi-information-outline"
                btn_label = " Details"
                btn_title = "View details"
            elif match_type == "librenms_id" and validation.get("librenms_id_needs_migration"):
                btn_class = "btn-outline-warning"
                btn_icon = "mdi-database-alert"
                btn_label = " Legacy ID"
                btn_title = "View legacy ID migration details"
            else:
                btn_class = "btn-outline-success"
                btn_icon = "mdi-check-circle"
                btn_label = ""
                btn_title = "View details"
            aria_attr = f'aria-label="{btn_title}" '
            buttons.append(
                f'<button type="button" '
                f'class="btn btn-sm {btn_class}" '
                f"{aria_attr}"
                f'hx-get="{details_url}" '
                f'hx-include="[name=cluster_{device_id}], [name=role_{device_id}], [name=rack_{device_id}], #use-sysname-toggle, #strip-domain-toggle" '
                f'hx-target="#htmx-modal-content" '
                f'hx-swap="innerHTML" '
                f'title="{btn_title}">'
                f'<i class="mdi {btn_icon}"></i>{btn_label}</button>'
            )
        elif is_ready:
            # Ready to import - show Import and Details buttons
            details_url = self._build_validation_details_url(device_id, validation)

            buttons.append(
                f'<button type="button" '
                f'class="btn btn-sm btn-success device-import-btn device-ready" '
                f'data-device-id="{device_id}" '
                f'data-import-mode="single"{vc_attributes} '
                f'title="Import this device">'
                f'<i class="mdi mdi-download"></i> Import</button>'
            )
            buttons.append(
                f'<button type="button" '
                f'class="btn btn-sm btn-outline-primary" '
                f'aria-label="View details" '
                f'hx-get="{details_url}" '
                f'hx-include="[name=cluster_{device_id}], [name=role_{device_id}], [name=rack_{device_id}], #use-sysname-toggle, #strip-domain-toggle" '
                f'hx-target="#htmx-modal-content" '
                f'hx-swap="innerHTML" '
                f'title="View details">'
                f'<i class="mdi mdi-information-outline"></i></button>'
            )
        elif can_import:
            # Has warnings - show Review button with Details
            details_url = self._build_validation_details_url(device_id, validation)

            buttons.append(
                f'<button type="button" '
                f'class="btn btn-sm btn-warning" '
                f'hx-get="{details_url}" '
                f'hx-include="[name=cluster_{device_id}], [name=role_{device_id}], [name=rack_{device_id}], #use-sysname-toggle, #strip-domain-toggle" '
                f'hx-target="#htmx-modal-content" '
                f'hx-swap="innerHTML" '
                f'title="Review and import">'
                f'<i class="mdi mdi-alert"></i> Review</button>'
            )
        else:
            # Cannot import (usually missing role) - show Import button (disabled until role selected) and Details
            details_url = self._build_validation_details_url(device_id, validation)

            buttons.append(
                f'<button type="button" '
                f'class="btn btn-sm btn-success device-import-btn" '
                f'data-device-id="{device_id}" '
                f"disabled{vc_attributes} "
                f'title="Select a role to enable import">'
                f'<i class="mdi mdi-download"></i> Import</button>'
            )
            buttons.append(
                f'<button type="button" '
                f'class="btn btn-sm btn-outline-danger" '
                f'hx-get="{details_url}" '
                f'hx-include="[name=cluster_{device_id}], [name=role_{device_id}], [name=rack_{device_id}], #use-sysname-toggle, #strip-domain-toggle" '
                f'hx-target="#htmx-modal-content" '
                f'hx-swap="innerHTML" '
                f'title="View validation details">'
                f'<i class="mdi mdi-alert-circle"></i> Details</button>'
            )

        return mark_safe('<div class="btn-group btn-group-sm">' + " ".join(buttons) + "</div>")

    def render_virtual_chassis(self, value, record):
        """Render Virtual Chassis status and details button."""
        validation = record.get("_validation", {})
        vc_data = validation.get("virtual_chassis", {})
        device_id = record.get("device_id")

        # Show dash for non-VC or single member stacks
        if not vc_data.get("is_stack") or vc_data.get("member_count", 0) <= 1:
            return mark_safe('<span class="text-muted">—</span>')

        vc_url = reverse(
            "plugins:netbox_librenms_plugin:device_vc_details",
            kwargs={"device_id": device_id},
        )

        # Show error button if detection failed
        if vc_data.get("detection_error"):
            return mark_safe(
                f'<button type="button" '
                f'class="btn btn-sm btn-outline-warning" '
                f'hx-get="{vc_url}" '
                f'hx-target="#htmx-modal-content" '
                f'hx-swap="innerHTML" '
                f'title="View virtual chassis error details">'
                f'<i class="mdi mdi-alert"></i> Error</button>'
            )

        # Show member count button for valid multi-member stacks
        member_count = vc_data.get("member_count", 0)
        return mark_safe(
            f'<button type="button" '
            f'class="btn btn-sm btn-outline-info" '
            f'hx-get="{vc_url}" '
            f'hx-target="#htmx-modal-content" '
            f'hx-swap="innerHTML" '
            f'title="View virtual chassis details">'
            f'<i class="mdi mdi-server-network"></i> {member_count} members</button>'
        )

    @staticmethod
    def _build_validation_details_url(device_id: int, validation: dict) -> str:
        """
        Build validation details URL with appropriate query parameters.

        Constructs the URL for the device validation details modal, adding
        cluster_id, role_id, and VC detection flag as query parameters.

        Args:
            device_id: LibreNMS device ID
            validation: Validation dict from validate_device_for_import()

        Returns:
            str: Complete URL with query parameters
        """
        details_url = reverse(
            "plugins:netbox_librenms_plugin:device_validation_details",
            kwargs={"device_id": device_id},
        )

        # Build query params based on import type
        params = []

        # Add cluster_id if this is a VM import
        if validation.get("cluster", {}).get("found") and validation.get("cluster", {}).get("cluster"):
            cluster_id = validation["cluster"]["cluster"].id
            params.append(f"cluster_id={cluster_id}")
        # Add role_id if device role is found
        elif validation.get("device_role", {}).get("found") and validation.get("device_role", {}).get("role"):
            role_id = validation["device_role"]["role"].id
            params.append(f"role_id={role_id}")

        # Add VC detection flag if it was enabled during initial load
        if validation.get("_vc_detection_enabled"):
            params.append("enable_vc_detection=true")

        if params:
            details_url += "?" + "&".join(params)

        return details_url

    @staticmethod
    def _build_vc_attributes(validation: dict, record: dict) -> str:
        vc_data = validation.get("virtual_chassis") or {}
        if not vc_data.get("is_stack"):
            return ' data-vc-is-stack="false"'

        members_payload = []
        for member in vc_data.get("members", []):
            members_payload.append(
                {
                    "position": member.get("position"),
                    "serial": member.get("serial"),
                    "suggested_name": member.get("suggested_name"),
                }
            )

        payload = {
            "member_count": vc_data.get("member_count", len(members_payload)),
            "members": members_payload,
            "detection_error": vc_data.get("detection_error"),
        }

        payload_json = escape(json.dumps(payload))
        master_name = record.get("hostname") or record.get("sysName") or ""
        master_value = escape(master_name)

        return (
            ' data-vc-is-stack="true"'
            f' data-vc-member-count="{payload["member_count"]}"'
            f' data-vc-info="{payload_json}"'
            f' data-vc-master="{master_value}"'
        )

    class Meta:
        """Meta options for DeviceImportTable."""

        # No model - we're working with LibreNMS API dictionaries, not Django model instances
        # This prevents NetBoxTable from auto-adding custom fields from Device model

        # Add row attributes to give each row a unique ID for HTMX targeting
        row_attrs = {
            "id": lambda record: f"device-row-{record.get('device_id')}",
        }

        fields = (
            "selection",
            "hostname",
            "sysname",
            "location",
            "hardware",
            "netbox_cluster",
            "netbox_role",
            "netbox_rack",
            "virtual_chassis",
            "actions",
        )
        sequence = (
            "selection",
            "hostname",
            "sysname",
            "location",
            "hardware",
            "netbox_cluster",
            "netbox_role",
            "netbox_rack",
            "virtual_chassis",
            "actions",
        )
        default_columns = fields
        orderable = True
        attrs = {
            "class": "table table-hover",
            "id": "device-import-table",
        }
