"""
Tests for background job implementation.

Tests the FilterDevicesJob, ImportDevicesJob, should_use_background_job logic,
job result loading, and graceful fallback behavior.

Refactored to use pure pytest without Django database dependencies.
All tests use mocking and direct attribute manipulation instead of HTTP requests.
"""

from unittest.mock import MagicMock, patch


class TestShouldUseBackgroundJob:
    """Test background job decision logic."""

    def test_checkbox_checked_returns_true(self):
        """When use_background_job form field is True, return True for superusers."""
        from netbox_librenms_plugin.views.imports.list import LibreNMSImportView

        view = LibreNMSImportView()
        view._filter_form_data = {"use_background_job": True}
        view.request = MagicMock()
        view.request.user.is_superuser = True

        assert view.should_use_background_job() is True

    def test_checkbox_unchecked_returns_false(self):
        """When use_background_job form field is False, return False."""
        from netbox_librenms_plugin.views.imports.list import LibreNMSImportView

        view = LibreNMSImportView()
        view._filter_form_data = {"use_background_job": False}
        view.request = MagicMock()
        view.request.user.is_superuser = True

        assert view.should_use_background_job() is False

    def test_default_when_field_missing(self):
        """When field is missing, default to True for superusers."""
        from netbox_librenms_plugin.views.imports.list import LibreNMSImportView

        view = LibreNMSImportView()
        view._filter_form_data = {"some_other_field": "value"}
        view.request = MagicMock()
        view.request.user.is_superuser = True

        assert view.should_use_background_job() is True

    def test_empty_form_data_returns_default(self):
        """Empty form data returns default True for superusers."""
        from netbox_librenms_plugin.views.imports.list import LibreNMSImportView

        view = LibreNMSImportView()
        view._filter_form_data = {}
        view.request = MagicMock()
        view.request.user.is_superuser = True

        assert view.should_use_background_job() is True

    def test_non_superuser_always_returns_false(self):
        """Non-superuser users always get synchronous mode."""
        from netbox_librenms_plugin.views.imports.list import LibreNMSImportView

        view = LibreNMSImportView()
        view._filter_form_data = {"use_background_job": True}
        view.request = MagicMock()
        view.request.user.is_superuser = False

        # Even when checkbox is True, non-superusers get False
        assert view.should_use_background_job() is False


def create_mock_job_runner(job_class, job_pk=123):
    """Create a mock job runner instance without invoking real __init__."""
    # Create instance without calling __init__
    job = object.__new__(job_class)
    # Set up required attributes
    job.job = MagicMock()
    job.job.pk = job_pk
    job.job.data = {}
    job.logger = MagicMock()
    return job


class TestFilterDevicesJob:
    """Test FilterDevicesJob background job."""

    @patch("netbox_librenms_plugin.librenms_api.LibreNMSAPI")
    @patch("netbox_librenms_plugin.import_utils.process_device_filters")
    def test_run_processes_filters_successfully(self, mock_process, mock_api_class):
        """Job runs and processes filters correctly."""
        from netbox_librenms_plugin.jobs import FilterDevicesJob

        # Setup mocks
        mock_api = MagicMock()
        mock_api.cache_timeout = 300
        mock_api.server_key = "default"
        mock_api_class.return_value = mock_api

        validated_devices = [
            {"device_id": 1, "hostname": "test1", "_validation": {}},
            {"device_id": 2, "hostname": "test2", "_validation": {}},
        ]
        mock_process.return_value = validated_devices

        # Create job instance without calling real __init__
        job = create_mock_job_runner(FilterDevicesJob)

        # Run job
        filters = {"location": "site1"}
        job.run(
            filters=filters,
            vc_detection_enabled=True,
            clear_cache=False,
            show_disabled=False,
        )

        # Verify process_device_filters was called with correct args
        mock_process.assert_called_once()
        call_kwargs = mock_process.call_args.kwargs
        assert call_kwargs["filters"] == filters
        assert call_kwargs["vc_detection_enabled"] is True
        assert call_kwargs["clear_cache"] is False
        assert call_kwargs["job"] == job

    @patch("netbox_librenms_plugin.librenms_api.LibreNMSAPI")
    @patch("netbox_librenms_plugin.import_utils.process_device_filters")
    def test_run_with_vc_detection_enabled(self, mock_process, mock_api_class):
        """vc_detection_enabled=True passed to processor."""
        from netbox_librenms_plugin.jobs import FilterDevicesJob

        mock_api = MagicMock()
        mock_api.cache_timeout = 300
        mock_api_class.return_value = mock_api
        mock_process.return_value = []

        job = create_mock_job_runner(FilterDevicesJob)

        job.run(
            filters={},
            vc_detection_enabled=True,
            clear_cache=False,
            show_disabled=False,
        )

        call_kwargs = mock_process.call_args.kwargs
        assert call_kwargs["vc_detection_enabled"] is True

    @patch("netbox_librenms_plugin.librenms_api.LibreNMSAPI")
    @patch("netbox_librenms_plugin.import_utils.process_device_filters")
    def test_run_with_clear_cache(self, mock_process, mock_api_class):
        """clear_cache=True triggers cache refresh."""
        from netbox_librenms_plugin.jobs import FilterDevicesJob

        mock_api = MagicMock()
        mock_api.cache_timeout = 300
        mock_api_class.return_value = mock_api
        mock_process.return_value = []

        job = create_mock_job_runner(FilterDevicesJob)

        job.run(
            filters={},
            vc_detection_enabled=False,
            clear_cache=True,
            show_disabled=False,
        )

        call_kwargs = mock_process.call_args.kwargs
        assert call_kwargs["clear_cache"] is True

    @patch("netbox_librenms_plugin.librenms_api.LibreNMSAPI")
    @patch("netbox_librenms_plugin.import_utils.process_device_filters")
    def test_run_with_show_disabled(self, mock_process, mock_api_class):
        """show_disabled=True includes disabled devices."""
        from netbox_librenms_plugin.jobs import FilterDevicesJob

        mock_api = MagicMock()
        mock_api.cache_timeout = 300
        mock_api_class.return_value = mock_api
        mock_process.return_value = []

        job = create_mock_job_runner(FilterDevicesJob)

        job.run(
            filters={},
            vc_detection_enabled=False,
            clear_cache=False,
            show_disabled=True,
        )

        call_kwargs = mock_process.call_args.kwargs
        assert call_kwargs["show_disabled"] is True

    @patch("netbox_librenms_plugin.librenms_api.LibreNMSAPI")
    @patch("netbox_librenms_plugin.import_utils.process_device_filters")
    def test_run_with_exclude_existing(self, mock_process, mock_api_class):
        """exclude_existing=True filters out NetBox devices."""
        from netbox_librenms_plugin.jobs import FilterDevicesJob

        mock_api = MagicMock()
        mock_api.cache_timeout = 300
        mock_api_class.return_value = mock_api
        mock_process.return_value = []

        job = create_mock_job_runner(FilterDevicesJob)

        job.run(
            filters={},
            vc_detection_enabled=False,
            clear_cache=False,
            show_disabled=False,
            exclude_existing=True,
        )

        call_kwargs = mock_process.call_args.kwargs
        assert call_kwargs["exclude_existing"] is True

    @patch("netbox_librenms_plugin.librenms_api.LibreNMSAPI")
    @patch("netbox_librenms_plugin.import_utils.process_device_filters")
    def test_run_with_custom_server_key(self, mock_process, mock_api_class):
        """Non-default server_key used for API."""
        from netbox_librenms_plugin.jobs import FilterDevicesJob

        mock_api = MagicMock()
        mock_api.cache_timeout = 300
        mock_api.server_key = "secondary"
        mock_api_class.return_value = mock_api
        mock_process.return_value = [{"device_id": 1, "hostname": "test1"}]

        job = create_mock_job_runner(FilterDevicesJob)

        job.run(
            filters={},
            vc_detection_enabled=False,
            clear_cache=False,
            show_disabled=False,
            server_key="secondary",
        )

        # Verify API was initialized with correct server_key
        mock_api_class.assert_called_once_with(server_key="secondary")
        # Verify server_key stored in job data
        assert job.job.data["server_key"] == "secondary"

    @patch("netbox_librenms_plugin.librenms_api.LibreNMSAPI")
    @patch("netbox_librenms_plugin.import_utils.process_device_filters")
    def test_run_stores_job_data_correctly(self, mock_process, mock_api_class):
        """Job stores expected data structure."""
        from netbox_librenms_plugin.jobs import FilterDevicesJob

        mock_api = MagicMock()
        mock_api.cache_timeout = 300
        mock_api.server_key = "secondary"
        mock_api_class.return_value = mock_api

        mock_process.return_value = [
            {"device_id": 1, "hostname": "test1"},
            {"device_id": 2, "hostname": "test2"},
        ]

        job = create_mock_job_runner(FilterDevicesJob, job_pk=456)

        job.run(
            filters={"location": "dc1"},
            vc_detection_enabled=True,
            clear_cache=False,
            show_disabled=False,
            server_key="secondary",
        )

        # Verify job.data structure
        assert job.job.data["device_ids"] == [1, 2]
        assert job.job.data["total_processed"] == 2
        assert job.job.data["filters"] == {"location": "dc1"}
        assert job.job.data["server_key"] == "secondary"
        assert job.job.data["vc_detection_enabled"] is True
        assert job.job.data["cache_timeout"] == 300
        assert "cached_at" in job.job.data
        assert job.job.data["completed"] is True
        job.job.save.assert_called()

    @patch("netbox_librenms_plugin.librenms_api.LibreNMSAPI")
    @patch("netbox_librenms_plugin.import_utils.process_device_filters")
    def test_run_handles_empty_results(self, mock_process, mock_api_class):
        """Empty filter results handled gracefully."""
        from netbox_librenms_plugin.jobs import FilterDevicesJob

        mock_api = MagicMock()
        mock_api.cache_timeout = 300
        mock_api_class.return_value = mock_api

        mock_process.return_value = []

        job = create_mock_job_runner(FilterDevicesJob, job_pk=789)

        job.run(
            filters={"location": "nonexistent"},
            vc_detection_enabled=False,
            clear_cache=False,
            show_disabled=False,
        )

        # Verify job data shows zero devices
        assert job.job.data["device_ids"] == []
        assert job.job.data["total_processed"] == 0
        assert job.job.data["completed"] is True

    @patch("netbox_librenms_plugin.librenms_api.LibreNMSAPI")
    @patch("netbox_librenms_plugin.import_utils.process_device_filters")
    def test_run_logs_progress(self, mock_process, mock_api_class):
        """Logger called with expected messages."""
        from netbox_librenms_plugin.jobs import FilterDevicesJob

        mock_api = MagicMock()
        mock_api.cache_timeout = 300
        mock_api_class.return_value = mock_api
        mock_process.return_value = [{"device_id": 1, "hostname": "test1"}]

        job = create_mock_job_runner(FilterDevicesJob)

        job.run(
            filters={"location": "site1"},
            vc_detection_enabled=True,
            clear_cache=False,
            show_disabled=False,
        )

        # Verify logger was called with expected messages
        assert job.logger.info.call_count >= 3
        info_calls = [call[0][0] for call in job.logger.info.call_args_list]
        assert any("Starting" in msg for msg in info_calls)
        assert any("completed" in msg.lower() for msg in info_calls)

    def test_job_meta_name(self):
        """Job has correct Meta.name."""
        from netbox_librenms_plugin.jobs import FilterDevicesJob

        assert FilterDevicesJob.Meta.name == "LibreNMS Device Filter"


class TestImportDevicesJob:
    """Test ImportDevicesJob background job."""

    @patch("netbox_librenms_plugin.import_utils.bulk_import_vms")
    @patch("netbox_librenms_plugin.import_utils.bulk_import_devices_shared")
    @patch("netbox_librenms_plugin.librenms_api.LibreNMSAPI")
    def test_run_device_only_import(self, mock_api_class, mock_bulk_devices, mock_bulk_vms):
        """Import devices without VMs."""
        from netbox_librenms_plugin.jobs import ImportDevicesJob

        mock_api_class.return_value = MagicMock()

        # Mock successful device imports
        mock_device_1 = MagicMock()
        mock_device_1.pk = 100
        mock_device_2 = MagicMock()
        mock_device_2.pk = 101

        mock_bulk_devices.return_value = {
            "success": [
                {"device": mock_device_1, "device_id": 1},
                {"device": mock_device_2, "device_id": 2},
            ],
            "failed": [],
            "skipped": [],
            "virtual_chassis_created": 0,
        }

        job = create_mock_job_runner(ImportDevicesJob, job_pk=789)

        job.run(
            device_ids=[1, 2],
            vm_imports={},
            server_key="default",
            sync_options={"sync_interfaces": True},
        )

        # Verify device import was called
        mock_bulk_devices.assert_called_once()
        # VM import should not be called with empty dict
        mock_bulk_vms.assert_not_called()

        # Verify job.data
        assert job.job.data["imported_device_pks"] == [100, 101]
        assert job.job.data["imported_vm_pks"] == []
        assert job.job.data["success_count"] == 2
        assert job.job.data["failed_count"] == 0

    @patch("netbox_librenms_plugin.import_utils.bulk_import_vms")
    @patch("netbox_librenms_plugin.import_utils.bulk_import_devices_shared")
    @patch("netbox_librenms_plugin.librenms_api.LibreNMSAPI")
    def test_run_vm_only_import(self, mock_api_class, mock_bulk_devices, mock_bulk_vms):
        """Import VMs without devices."""
        from netbox_librenms_plugin.jobs import ImportDevicesJob

        mock_api_class.return_value = MagicMock()

        # Mock successful VM imports
        mock_vm_1 = MagicMock()
        mock_vm_1.pk = 200
        mock_vm_2 = MagicMock()
        mock_vm_2.pk = 201

        mock_bulk_vms.return_value = {
            "success": [
                {"device": mock_vm_1, "device_id": 10},
                {"device": mock_vm_2, "device_id": 11},
            ],
            "failed": [],
            "skipped": [],
        }

        job = create_mock_job_runner(ImportDevicesJob, job_pk=790)

        job.run(
            device_ids=[],
            vm_imports={10: {"cluster_id": 1}, 11: {"cluster_id": 1}},
            server_key="default",
        )

        # Verify device import was not called with empty list
        mock_bulk_devices.assert_not_called()
        # VM import should be called
        mock_bulk_vms.assert_called_once()

        # Verify job.data
        assert job.job.data["imported_device_pks"] == []
        assert job.job.data["imported_vm_pks"] == [200, 201]
        assert job.job.data["success_count"] == 2

    @patch("netbox_librenms_plugin.import_utils.bulk_import_vms")
    @patch("netbox_librenms_plugin.import_utils.bulk_import_devices_shared")
    @patch("netbox_librenms_plugin.librenms_api.LibreNMSAPI")
    def test_run_mixed_device_and_vm_import(self, mock_api_class, mock_bulk_devices, mock_bulk_vms):
        """Import both devices and VMs."""
        from netbox_librenms_plugin.jobs import ImportDevicesJob

        mock_api = MagicMock()
        mock_api.server_key = "non-default"
        mock_api_class.return_value = mock_api

        # Mock device imports
        mock_device = MagicMock()
        mock_device.pk = 100

        mock_bulk_devices.return_value = {
            "success": [{"device": mock_device, "device_id": 1}],
            "failed": [],
            "skipped": [],
            "virtual_chassis_created": 0,
            "cancelled": False,
        }

        # Mock VM imports
        mock_vm = MagicMock()
        mock_vm.pk = 200

        mock_bulk_vms.return_value = {
            "success": [{"device": mock_vm, "device_id": 10}],
            "failed": [],
            "skipped": [],
        }

        job = create_mock_job_runner(ImportDevicesJob, job_pk=791)

        job.run(
            device_ids=[1],
            vm_imports={10: {"cluster_id": 1}},
            server_key="non-default",
        )

        # Both should be called
        mock_bulk_devices.assert_called_once()
        mock_bulk_vms.assert_called_once()

        # Verify server_key (via api.server_key) is forwarded to bulk_import_devices_shared
        bulk_devices_kwargs = mock_bulk_devices.call_args[1]
        assert bulk_devices_kwargs.get("server_key") == "non-default"

        # Verify bulk_import_vms received the api with the correct server_key
        bulk_vms_positional = mock_bulk_vms.call_args[0]
        assert bulk_vms_positional[1].server_key == "non-default"

        # Verify combined results
        assert job.job.data["imported_device_pks"] == [100]
        assert job.job.data["imported_vm_pks"] == [200]
        assert job.job.data["success_count"] == 2
        assert job.job.data["total"] == 2

    @patch("netbox_librenms_plugin.import_utils.bulk_import_vms")
    @patch("netbox_librenms_plugin.import_utils.bulk_import_devices_shared")
    @patch("netbox_librenms_plugin.librenms_api.LibreNMSAPI")
    def test_run_with_sync_options(self, mock_api_class, mock_bulk_devices, mock_bulk_vms):
        """Sync options passed to bulk import."""
        from netbox_librenms_plugin.jobs import ImportDevicesJob

        mock_api_class.return_value = MagicMock()

        mock_bulk_devices.return_value = {
            "success": [],
            "failed": [],
            "skipped": [],
            "virtual_chassis_created": 0,
        }

        job = create_mock_job_runner(ImportDevicesJob, job_pk=792)

        sync_options = {
            "sync_interfaces": True,
            "sync_cables": False,
            "sync_ips": True,
            "use_sysname": True,
            "strip_domain": True,
        }

        job.run(
            device_ids=[1],
            vm_imports={},
            server_key="default",
            sync_options=sync_options,
        )

        # Verify sync_options passed to bulk_import_devices_shared
        call_kwargs = mock_bulk_devices.call_args.kwargs
        assert call_kwargs["sync_options"] == sync_options

    @patch("netbox_librenms_plugin.import_utils.bulk_import_vms")
    @patch("netbox_librenms_plugin.import_utils.bulk_import_devices_shared")
    @patch("netbox_librenms_plugin.librenms_api.LibreNMSAPI")
    def test_run_with_manual_mappings(self, mock_api_class, mock_bulk_devices, mock_bulk_vms):
        """Manual mappings passed correctly."""
        from netbox_librenms_plugin.jobs import ImportDevicesJob

        mock_api_class.return_value = MagicMock()

        mock_bulk_devices.return_value = {
            "success": [],
            "failed": [],
            "skipped": [],
            "virtual_chassis_created": 0,
        }

        job = create_mock_job_runner(ImportDevicesJob, job_pk=793)

        manual_mappings = {
            1: {"site_id": 10, "device_role_id": 5},
            2: {"site_id": 11, "device_role_id": 6},
        }

        job.run(
            device_ids=[1, 2],
            vm_imports={},
            manual_mappings_per_device=manual_mappings,
        )

        # Verify manual_mappings passed to bulk_import_devices_shared
        call_kwargs = mock_bulk_devices.call_args.kwargs
        assert call_kwargs["manual_mappings_per_device"] == manual_mappings

    @patch("netbox_librenms_plugin.import_utils.bulk_import_vms")
    @patch("netbox_librenms_plugin.import_utils.bulk_import_devices_shared")
    @patch("netbox_librenms_plugin.librenms_api.LibreNMSAPI")
    def test_run_stores_imported_pks(self, mock_api_class, mock_bulk_devices, mock_bulk_vms):
        """Imported device/VM PKs stored in job.data."""
        from netbox_librenms_plugin.jobs import ImportDevicesJob

        mock_api_class.return_value = MagicMock()

        mock_device = MagicMock()
        mock_device.pk = 100

        mock_bulk_devices.return_value = {
            "success": [{"device": mock_device, "device_id": 1}],
            "failed": [],
            "skipped": [],
            "virtual_chassis_created": 0,
        }

        job = create_mock_job_runner(ImportDevicesJob, job_pk=794)

        job.run(device_ids=[1], vm_imports={})

        assert 100 in job.job.data["imported_device_pks"]

    @patch("netbox_librenms_plugin.import_utils.bulk_import_vms")
    @patch("netbox_librenms_plugin.import_utils.bulk_import_devices_shared")
    @patch("netbox_librenms_plugin.librenms_api.LibreNMSAPI")
    def test_run_stores_libre_device_ids(self, mock_api_class, mock_bulk_devices, mock_bulk_vms):
        """LibreNMS device IDs stored for re-render."""
        from netbox_librenms_plugin.jobs import ImportDevicesJob

        mock_api_class.return_value = MagicMock()

        mock_device = MagicMock()
        mock_device.pk = 100

        mock_bulk_devices.return_value = {
            "success": [{"device": mock_device, "device_id": 42}],
            "failed": [],
            "skipped": [],
            "virtual_chassis_created": 0,
        }

        job = create_mock_job_runner(ImportDevicesJob, job_pk=795)

        job.run(device_ids=[42], vm_imports={})

        assert 42 in job.job.data["imported_libre_device_ids"]

    @patch("netbox_librenms_plugin.import_utils.bulk_import_vms")
    @patch("netbox_librenms_plugin.import_utils.bulk_import_devices_shared")
    @patch("netbox_librenms_plugin.librenms_api.LibreNMSAPI")
    def test_run_aggregates_errors(self, mock_api_class, mock_bulk_devices, mock_bulk_vms):
        """Device and VM errors are combined in job.data."""
        from netbox_librenms_plugin.jobs import ImportDevicesJob

        mock_api_class.return_value = MagicMock()

        # Mock mixed results
        mock_bulk_devices.return_value = {
            "success": [],
            "failed": [{"device_id": 1, "error": "Device type not found"}],
            "skipped": [],
            "virtual_chassis_created": 0,
        }
        mock_bulk_vms.return_value = {
            "success": [],
            "failed": [{"device_id": 10, "error": "Cluster not specified"}],
            "skipped": [],
        }

        job = create_mock_job_runner(ImportDevicesJob, job_pk=999)

        job.run(
            device_ids=[1],
            vm_imports={10: {"cluster": None}},
        )

        # Verify errors aggregated
        assert len(job.job.data["errors"]) == 2
        assert job.job.data["failed_count"] == 2
        assert job.job.data["success_count"] == 0

    @patch("netbox_librenms_plugin.import_utils.bulk_import_vms")
    @patch("netbox_librenms_plugin.import_utils.bulk_import_devices_shared")
    @patch("netbox_librenms_plugin.librenms_api.LibreNMSAPI")
    def test_run_handles_all_failures(self, mock_api_class, mock_bulk_devices, mock_bulk_vms):
        """All imports fail gracefully."""
        from netbox_librenms_plugin.jobs import ImportDevicesJob

        mock_api_class.return_value = MagicMock()

        mock_bulk_devices.return_value = {
            "success": [],
            "failed": [
                {"device_id": 1, "error": "Error 1"},
                {"device_id": 2, "error": "Error 2"},
            ],
            "skipped": [],
            "virtual_chassis_created": 0,
        }

        job = create_mock_job_runner(ImportDevicesJob, job_pk=800)

        job.run(device_ids=[1, 2], vm_imports={})

        # Should complete without exception
        assert job.job.data["success_count"] == 0
        assert job.job.data["failed_count"] == 2
        assert job.job.data["completed"] is True
        job.job.save.assert_called()

    def test_job_meta_name(self):
        """Job has correct Meta.name."""
        from netbox_librenms_plugin.jobs import ImportDevicesJob

        assert ImportDevicesJob.Meta.name == "LibreNMS Device Import"


class TestLoadJobResults:
    """Test loading results from completed background jobs."""

    @patch("netbox_librenms_plugin.views.imports.list.cache")
    @patch("netbox_librenms_plugin.import_utils.get_validated_device_cache_key")
    @patch("core.models.Job")
    def test_load_success_uses_correct_cache_keys(self, mock_job_class, mock_get_key, mock_cache):
        """Load uses get_validated_device_cache_key with job data."""
        from netbox_librenms_plugin.views.imports.list import LibreNMSImportView

        # Setup mock job
        mock_job = MagicMock()
        mock_job.status = "completed"
        mock_job.data = {
            "device_ids": [1, 2],
            "filters": {"location": "dc1"},
            "server_key": "primary",
            "vc_detection_enabled": True,
            "cached_at": "2026-01-20T10:00:00Z",
            "cache_timeout": 600,
            "use_sysname": True,
            "strip_domain": False,
        }
        mock_job_class.objects.get.return_value = mock_job

        # Mock cache key generation
        mock_get_key.side_effect = lambda **kw: f"key_{kw['device_id']}"

        # Mock cache returns
        mock_cache.get.side_effect = [
            {"device_id": 1, "hostname": "test1"},
            {"device_id": 2, "hostname": "test2"},
        ]

        view = LibreNMSImportView()
        results = view._load_job_results(123)

        # Verify cache key function called with correct params
        assert mock_get_key.call_count == 2
        mock_get_key.assert_any_call(
            server_key="primary",
            filters={"location": "dc1"},
            device_id=1,
            vc_enabled=True,
            use_sysname=True,
            strip_domain=False,
        )
        mock_get_key.assert_any_call(
            server_key="primary",
            filters={"location": "dc1"},
            device_id=2,
            vc_enabled=True,
            use_sysname=True,
            strip_domain=False,
        )

        assert len(results) == 2

    @patch("netbox_librenms_plugin.views.imports.list.cache")
    @patch("netbox_librenms_plugin.import_utils.get_validated_device_cache_key")
    @patch("core.models.Job")
    def test_load_extracts_filters_from_job_data(self, mock_job_class, mock_get_key, mock_cache):
        """Filters, server_key, vc_enabled extracted from job data."""
        from netbox_librenms_plugin.views.imports.list import LibreNMSImportView

        mock_job = MagicMock()
        mock_job.status = "completed"
        mock_job.data = {
            "device_ids": [1],
            "filters": {"location": "dc2", "type": "router"},
            "server_key": "secondary",
            "vc_detection_enabled": False,
            "cached_at": "2026-01-20T10:00:00Z",
            "cache_timeout": 300,
            "use_sysname": True,
            "strip_domain": False,
        }
        mock_job_class.objects.get.return_value = mock_job
        mock_get_key.return_value = "test_key"
        mock_cache.get.return_value = {"device_id": 1}

        view = LibreNMSImportView()
        view._load_job_results(456)

        # Verify get_validated_device_cache_key called with extracted values
        mock_get_key.assert_called_once_with(
            server_key="secondary",
            filters={"location": "dc2", "type": "router"},
            device_id=1,
            vc_enabled=False,
            use_sysname=True,
            strip_domain=False,
        )

    @patch("netbox_librenms_plugin.views.imports.list.cache")
    @patch("netbox_librenms_plugin.import_utils.get_validated_device_cache_key")
    @patch("core.models.Job")
    def test_load_returns_cached_devices(self, mock_job_class, mock_get_key, mock_cache):
        """Devices retrieved from cache."""
        from netbox_librenms_plugin.views.imports.list import LibreNMSImportView

        mock_job = MagicMock()
        mock_job.status = "completed"
        mock_job.data = {
            "device_ids": [1, 2],
            "filters": {},
            "server_key": "default",
            "vc_detection_enabled": False,
            "cached_at": "2026-01-20T10:00:00Z",
            "cache_timeout": 300,
        }
        mock_job_class.objects.get.return_value = mock_job
        mock_get_key.side_effect = lambda **kw: f"key_{kw['device_id']}"
        mock_cache.get.side_effect = [
            {"device_id": 1, "hostname": "device1"},
            {"device_id": 2, "hostname": "device2"},
        ]

        view = LibreNMSImportView()
        results = view._load_job_results(789)

        assert len(results) == 2
        assert results[0]["hostname"] == "device1"
        assert results[1]["hostname"] == "device2"

    @patch("netbox_librenms_plugin.views.imports.list.cache")
    @patch("netbox_librenms_plugin.import_utils.get_validated_device_cache_key")
    @patch("core.models.Job")
    def test_load_sets_cache_metadata(self, mock_job_class, mock_get_key, mock_cache):
        """Load sets _cache_timestamp and _cache_timeout on view."""
        from netbox_librenms_plugin.views.imports.list import LibreNMSImportView

        mock_job = MagicMock()
        mock_job.status = "completed"
        mock_job.data = {
            "device_ids": [1],
            "filters": {},
            "server_key": "default",
            "vc_detection_enabled": False,
            "cached_at": "2026-01-20T12:00:00Z",
            "cache_timeout": 900,
        }
        mock_job_class.objects.get.return_value = mock_job
        mock_get_key.return_value = "test_key"
        mock_cache.get.return_value = {"device_id": 1}

        view = LibreNMSImportView()
        view._load_job_results(456)

        assert view._cache_timestamp == "2026-01-20T12:00:00Z"
        assert view._cache_timeout == 900

    @patch("core.models.Job")
    def test_load_job_not_found_returns_empty(self, mock_job_class):
        """Non-existent job returns empty list."""
        from netbox_librenms_plugin.views.imports.list import LibreNMSImportView

        # Create a mock DoesNotExist exception
        mock_job_class.DoesNotExist = Exception
        mock_job_class.objects.get.side_effect = mock_job_class.DoesNotExist

        view = LibreNMSImportView()
        results = view._load_job_results(999)

        assert results == []

    @patch("core.models.Job")
    def test_load_job_not_completed_returns_empty(self, mock_job_class):
        """Running job returns empty list."""
        from netbox_librenms_plugin.views.imports.list import LibreNMSImportView

        mock_job = MagicMock()
        mock_job.status = "running"
        mock_job_class.objects.get.return_value = mock_job

        view = LibreNMSImportView()
        results = view._load_job_results(123)

        assert results == []

    @patch("netbox_librenms_plugin.views.imports.list.cache")
    @patch("netbox_librenms_plugin.import_utils.get_validated_device_cache_key")
    @patch("core.models.Job")
    def test_load_expired_cache_returns_empty(self, mock_job_class, mock_get_key, mock_cache):
        """All cache misses returns empty list."""
        from netbox_librenms_plugin.views.imports.list import LibreNMSImportView

        mock_job = MagicMock()
        mock_job.status = "completed"
        mock_job.data = {
            "device_ids": [1, 2],
            "filters": {},
            "server_key": "default",
            "vc_detection_enabled": False,
            "cached_at": "2026-01-20T10:00:00Z",
            "cache_timeout": 300,
        }
        mock_job_class.objects.get.return_value = mock_job
        mock_get_key.side_effect = lambda **kw: f"key_{kw['device_id']}"

        # Simulate expired cache (returns None)
        mock_cache.get.return_value = None

        view = LibreNMSImportView()
        results = view._load_job_results(123)

        assert results == []

    @patch("netbox_librenms_plugin.views.imports.list.cache")
    @patch("netbox_librenms_plugin.import_utils.get_validated_device_cache_key")
    @patch("core.models.Job")
    def test_load_partial_cache_returns_available(self, mock_job_class, mock_get_key, mock_cache):
        """Some expired, returns available devices."""
        from netbox_librenms_plugin.views.imports.list import LibreNMSImportView

        mock_job = MagicMock()
        mock_job.status = "completed"
        mock_job.data = {
            "device_ids": [1, 2, 3],
            "filters": {},
            "server_key": "default",
            "vc_detection_enabled": False,
            "cached_at": "2026-01-20T10:00:00Z",
            "cache_timeout": 300,
        }
        mock_job_class.objects.get.return_value = mock_job
        mock_get_key.side_effect = lambda **kw: f"key_{kw['device_id']}"

        # First device in cache, second expired, third in cache
        mock_cache.get.side_effect = [
            {"device_id": 1, "hostname": "device1"},
            None,  # Expired
            {"device_id": 3, "hostname": "device3"},
        ]

        view = LibreNMSImportView()
        results = view._load_job_results(123)

        # Should return available devices only
        assert len(results) == 2
        assert results[0]["device_id"] == 1
        assert results[1]["device_id"] == 3


class TestGracefulFallback:
    """Test graceful fallback when RQ workers unavailable."""

    @patch("netbox_librenms_plugin.views.imports.list.get_workers_for_queue")
    def test_no_workers_triggers_synchronous_processing(self, mock_get_workers):
        """No RQ workers triggers synchronous fallback."""
        mock_get_workers.return_value = 0

        # This test verifies the condition check, not full request handling
        from netbox_librenms_plugin.views.imports.list import get_workers_for_queue

        workers = get_workers_for_queue("default")
        assert workers == 0

        # When workers == 0, the code path skips job enqueuing
        # and falls through to synchronous get_queryset processing

    @patch("netbox_librenms_plugin.views.imports.list.get_workers_for_queue")
    def test_workers_available_allows_background_job(self, mock_get_workers):
        """Available workers allow background job enqueue."""
        mock_get_workers.return_value = 2

        from netbox_librenms_plugin.views.imports.list import get_workers_for_queue

        workers = get_workers_for_queue("default")
        assert workers > 0
        # When workers > 0, the code path proceeds to FilterDevicesJob.enqueue()

    @patch("netbox_librenms_plugin.views.imports.list.get_workers_for_queue")
    @patch("netbox_librenms_plugin.views.imports.list.logger")
    def test_fallback_logs_warning(self, mock_logger, mock_get_workers):
        """Warning logged when falling back (checked via worker count)."""
        mock_get_workers.return_value = 0

        # Verify the function returns 0 workers which would trigger fallback
        from netbox_librenms_plugin.views.imports.list import get_workers_for_queue

        workers = get_workers_for_queue("default")
        assert workers == 0

        # The view would log a warning when it detects no workers and falls back
        # This test verifies the condition that triggers the fallback path
