"""
Background jobs for LibreNMS plugin.

This module provides background job implementations for long-running operations
such as device filtering with Virtual Chassis detection.
"""

import logging

from netbox.jobs import JobRunner

logger = logging.getLogger(__name__)


class FilterDevicesJob(JobRunner):
    """
    Background job for processing LibreNMS device filters with VC detection.

    Background jobs provide several benefits over synchronous processing:
    - Active cancellation via NetBox Jobs interface
    - Browser remains responsive (no "page loading" state)
    - Job progress tracked in NetBox Jobs table
    - Results persist in cache for later retrieval

    Users control background job execution via the "Run as background job" checkbox
    in the filter form. When enabled, the job runs asynchronously; when disabled,
    filtering runs synchronously.

    Note: Both synchronous and background processing complete once started,
    even if the user navigates away. The key difference is cancellation ability
    and browser responsiveness.

    Results are cached individually per device to avoid exceeding job data size limits.
    """

    class Meta:
        """Meta options for FilterDevicesJob."""

        name = "LibreNMS Device Filter"

    def run(
        self,
        filters,
        vc_detection_enabled,
        clear_cache,
        show_disabled,
        exclude_existing=False,
        server_key=None,
        use_sysname=True,
        strip_domain=False,
        **kwargs,
    ):
        """
        Execute filter processing in background.

        Logs job start, completion, and any early termination events.

        Args:
            filters: Dict with location, type, os, hostname, sysname keys
            vc_detection_enabled: Whether to detect virtual chassis
            clear_cache: Whether to force cache refresh
            show_disabled: Whether to include disabled devices
            exclude_existing: Whether to exclude devices that already exist in NetBox
            server_key: Optional LibreNMS server key for multi-server setups
            use_sysname: If True, prefer sysName over hostname for device name resolution
            strip_domain: If True, strip domain suffix from device names
            **kwargs: Additional job parameters
        """
        from netbox_librenms_plugin.import_utils import process_device_filters
        from netbox_librenms_plugin.librenms_api import LibreNMSAPI

        self.logger.info("Starting LibreNMS device filter job")
        self.logger.info(f"Filters: {filters}")
        self.logger.info(f"VC detection: {vc_detection_enabled}")
        self.logger.info(f"Clear cache: {clear_cache}")
        self.logger.info(f"Show disabled: {show_disabled}")
        if exclude_existing:
            self.logger.info("Excluding existing devices")
        if server_key:
            self.logger.info(f"Using LibreNMS server: {server_key}")

        # Initialize API client
        api = LibreNMSAPI(server_key=server_key)
        self.logger.info(f"LibreNMS API initialized (cache timeout: {api.cache_timeout}s)")

        # Process filters using shared function
        validated_devices = process_device_filters(
            api=api,
            filters=filters,
            vc_detection_enabled=vc_detection_enabled,
            clear_cache=clear_cache,
            show_disabled=show_disabled,
            exclude_existing=exclude_existing,
            job=self,
            use_sysname=use_sysname,
            strip_domain=strip_domain,
        )

        # Store device IDs for result retrieval
        # Note: Validated devices are cached with shared keys by process_device_filters
        device_ids = [device["device_id"] for device in validated_devices]

        # Track cache timestamp for frontend expiration warnings
        from datetime import datetime, timezone

        cached_at = datetime.now(timezone.utc).isoformat()

        # Store only metadata in job data (not the full device list)
        # Devices are retrieved via shared cache keys in _load_job_results
        self.job.data = {
            "device_ids": device_ids,
            "total_processed": len(validated_devices),
            "filters": filters,
            "server_key": api.server_key,
            "vc_detection_enabled": vc_detection_enabled,
            "use_sysname": use_sysname,
            "strip_domain": strip_domain,
            "cache_timeout": api.cache_timeout,
            "cached_at": cached_at,
            "completed": True,
        }

        self.job.save(update_fields=["data"])

        self.logger.info(
            f"Job completed successfully. Processed {len(validated_devices)} devices. "
            f"Results available via shared cache for {api.cache_timeout} seconds."
        )


class ImportDevicesJob(JobRunner):
    """
    Background job for importing LibreNMS devices to NetBox.

    Handles bulk device/VM imports in the background to keep browser responsive.
    Benefits:
    - Active cancellation via NetBox Jobs interface
    - Browser remains responsive during large imports
    - Job progress tracked with device count logging
    - Errors collected per device without stopping entire import

    Users control background job execution via the "Run as background job" checkbox
    in the import confirmation modal. When enabled, the job runs asynchronously;
    when disabled, imports run synchronously.

    Results stored in job.data with structure:
    {
        "imported_device_pks": [1, 2, 3],  # NetBox Device PKs
        "imported_vm_pks": [10, 11],       # NetBox VirtualMachine PKs
        "total": 5,
        "success_count": 4,
        "failed_count": 1,
        "skipped_count": 0,
        "errors": [{"device_id": 123, "error": "..."}]
    }
    """

    class Meta:
        """Meta options for ImportDevicesJob."""

        name = "LibreNMS Device Import"

    def run(
        self,
        device_ids,
        vm_imports,
        server_key=None,
        sync_options=None,
        manual_mappings_per_device=None,
        libre_devices_cache=None,
        **kwargs,
    ):
        """
        Execute device/VM imports in background.

        Args:
            device_ids: List of LibreNMS device IDs to import as Devices
            vm_imports: Dict mapping device_id to cluster/role info for VM imports
            server_key: Optional LibreNMS server key for multi-server setups
            sync_options: Dict with sync_interfaces, sync_cables, sync_ips,
                use_sysname, strip_domain, and vc_detection_enabled
            manual_mappings_per_device: Dict mapping device_id to manual_mappings dict
            libre_devices_cache: Optional dict mapping device_id to pre-fetched device data
            **kwargs: Additional job parameters
        """

        from netbox_librenms_plugin.import_utils import (
            bulk_import_devices_shared,
        )
        from netbox_librenms_plugin.librenms_api import LibreNMSAPI

        total_count = len(device_ids) + len(vm_imports)
        self.logger.info(f"Starting LibreNMS import job for {total_count} devices/VMs")
        self.logger.info(f"Device imports: {len(device_ids)}, VM imports: {len(vm_imports)}")
        if server_key:
            self.logger.info(f"Using LibreNMS server: {server_key}")

        # Initialize API client
        api = LibreNMSAPI(server_key=server_key)

        # Import devices using shared function with job context
        device_result = {
            "success": [],
            "failed": [],
            "skipped": [],
            "virtual_chassis_created": 0,
        }
        if device_ids:
            self.logger.info(f"Importing {len(device_ids)} devices...")
            device_result = bulk_import_devices_shared(
                device_ids=device_ids,
                server_key=api.server_key,
                sync_options=sync_options,
                manual_mappings_per_device=manual_mappings_per_device,
                libre_devices_cache=libre_devices_cache,
                job=self,  # Pass job context for logging and cancellation
                user=self.job.user,  # Pass user for permission checks
            )

        # Import VMs
        vm_result = {"success": [], "failed": [], "skipped": []}
        if vm_imports:
            self.logger.info(f"Importing {len(vm_imports)} VMs...")
            from netbox_librenms_plugin.import_utils import bulk_import_vms

            vm_result = bulk_import_vms(
                vm_imports, api, sync_options, libre_devices_cache, job=self, user=self.job.user
            )

        # Combine results — partition device_result successes by model type since
        # bulk_import_devices_shared() may return VirtualMachine objects when import_as_vm=True.
        device_successes = []
        vm_successes = list(vm_result.get("success", []))
        for item in device_result.get("success", []):
            obj = item.get("device")
            if not obj:
                continue
            if obj._meta.model_name == "virtualmachine":
                vm_successes.append(item)
            else:
                device_successes.append(item)

        imported_device_pks = [item["device"].pk for item in device_successes]
        imported_vm_pks = [item["device"].pk for item in vm_successes]

        # Also store LibreNMS device IDs for re-rendering table rows
        imported_libre_device_ids = [item["device_id"] for item in device_successes]
        imported_libre_vm_ids = [item["device_id"] for item in vm_successes]

        success_count = len(device_result.get("success", [])) + len(vm_result.get("success", []))
        failed_count = len(device_result.get("failed", [])) + len(vm_result.get("failed", []))
        skipped_count = len(device_result.get("skipped", [])) + len(vm_result.get("skipped", []))

        all_errors = device_result.get("failed", []) + vm_result.get("failed", [])

        # Store results in job.data
        self.job.data = {
            "imported_device_pks": imported_device_pks,
            "imported_vm_pks": imported_vm_pks,
            "imported_libre_device_ids": imported_libre_device_ids,
            "imported_libre_vm_ids": imported_libre_vm_ids,
            "server_key": api.server_key,
            "total": total_count,
            "success_count": success_count,
            "failed_count": failed_count,
            "skipped_count": skipped_count,
            "virtual_chassis_created": device_result.get("virtual_chassis_created", 0),
            "errors": all_errors,
            "completed": True,
        }
        self.job.save(update_fields=["data"])

        self.logger.info(
            f"Import job completed. Success: {success_count}, Failed: {failed_count}, Skipped: {skipped_count}"
        )
