"""Virtual machine creation and import operations."""

import logging

from dcim.models import DeviceRole
from django.db import transaction
from django.utils import timezone
from virtualization.models import Cluster

from ..librenms_api import LibreNMSAPI
from .bulk_import import _is_job_cancelled
from .device_operations import _determine_device_name, fetch_device_with_cache, validate_device_for_import
from .permissions import require_permissions

logger = logging.getLogger(__name__)


def create_vm_from_librenms(
    libre_device: dict,
    validation: dict,
    server_key: str = "default",
    use_sysname: bool = True,
    strip_domain: bool = False,
    role=None,
):
    """
    Create a NetBox VirtualMachine from LibreNMS device data.

    Args:
        libre_device: Device data from LibreNMS
        validation: Validation result from validate_device_for_import with import_as_vm=True
        use_sysname: If True, prefer sysName; if False, use hostname
        server_key: LibreNMS server key used to store the librenms_id custom field

    Returns:
        Created VirtualMachine instance

    Raises:
        Exception if VM cannot be created
    """
    from virtualization.models import VirtualMachine

    if not validation["can_import"]:
        raise ValueError(f"VM cannot be imported: {', '.join(validation['issues'])}")

    # Extract matched objects from validation
    cluster = validation["cluster"]["cluster"]
    platform = validation["platform"].get("platform")
    role = role if role is not None else validation.get("device_role", {}).get("role")

    # Determine VM name - use pre-computed name if available (handles strip_domain),
    # falling back to the validated resolved_name before recomputing from raw fields.
    vm_name = libre_device.get("_computed_name") or validation.get("resolved_name")
    if not vm_name:
        vm_name = _determine_device_name(
            libre_device,
            use_sysname=use_sysname,
            strip_domain=strip_domain,
            device_id=libre_device.get("device_id"),
        )

    # Generate import timestamp comment
    import_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S %Z")

    # Validate device_id before creating the VM so a missing/invalid value
    # never leaves a VM without a librenms_id (partial persistence).
    raw_device_id = libre_device["device_id"]
    if isinstance(raw_device_id, bool):
        raise ValueError(f"device_id is a boolean ({raw_device_id!r}); expected an integer")
    librenms_device_id = int(raw_device_id)

    from ..utils import set_librenms_device_id

    # Create the VM and assign its LibreNMS ID atomically so a failure in
    # set_librenms_device_id never leaves a VM without a mapping.
    with transaction.atomic():
        vm = VirtualMachine.objects.create(
            name=vm_name,
            cluster=cluster,
            role=role,
            platform=platform,
            comments=f"Imported from LibreNMS (device_id={librenms_device_id}) by netbox-librenms-plugin on {import_time}",
        )
        set_librenms_device_id(vm, librenms_device_id, server_key)
        vm.save()

    logger.info(f"Created VM {vm.name} (ID: {vm.pk}) from LibreNMS device {libre_device['device_id']}")
    return vm


def bulk_import_vms(
    vm_imports: dict[int, dict[str, int]],
    api: LibreNMSAPI,
    sync_options: dict = None,
    libre_devices_cache: dict = None,
    job=None,
    user=None,
) -> dict:
    """
    Import multiple LibreNMS devices as VMs in NetBox.

    Handles validation, cluster/role assignment, name determination,
    and VM creation. Supports both synchronous and background job execution.

    This function consolidates VM import logic that was previously duplicated
    in BulkImportDevicesView and ImportDevicesJob, ensuring consistent behavior
    across synchronous and background import paths.

    Args:
        vm_imports: Dict mapping device_id to {"cluster_id": int, "device_role_id": int}
        api: LibreNMSAPI instance for device fetching
        sync_options: Optional dict with use_sysname, strip_domain settings
        libre_devices_cache: Optional pre-fetched device data cache
        job: Optional JobRunner instance for background job logging/cancellation
        user: User performing the import (for permission checks). If job is provided,
            user is extracted from job.job.user if not explicitly passed.

    Returns:
        Dict with keys:
            - success: List of {"device_id": int, "device": VM, "message": str}
            - failed: List of {"device_id": int, "error": str}
            - skipped: List of {"device_id": int, "reason": str}

    Raises:
        PermissionDenied: If user lacks required permissions

    Example:
        >>> # Synchronous import from view
        >>> vm_imports = {123: {"cluster_id": 5, "device_role_id": 2}}
        >>> result = bulk_import_vms(vm_imports, api, sync_options, user=request.user)
        >>> print(f"Created {len(result['success'])} VMs")
        >>>
        >>> # Background job import
        >>> result = bulk_import_vms(vm_imports, api, sync_options, cache, job=self)
    """
    from netbox_librenms_plugin.import_validation_helpers import (
        apply_cluster_to_validation,
        apply_role_to_validation,
    )

    # Extract user from job if not explicitly provided
    if user is None and job is not None:
        user = getattr(job.job, "user", None)

    # Check permissions at start of bulk operation
    require_permissions(user, ["virtualization.add_virtualmachine"], "import VMs")

    result = {"success": [], "failed": [], "skipped": []}
    vm_ids = list(vm_imports.keys())

    # Use job logger if available, otherwise standard logger
    log = job.logger if job else logger

    for idx, vm_id in enumerate(vm_ids, start=1):
        # Check for job cancellation before first VM and every 5 thereafter
        if job and (idx == 1 or idx % 5 == 0) and _is_job_cancelled(job):
            log.warning(f"Job cancelled at VM {idx} of {len(vm_ids)}")
            break
        log.info(f"Processing VM {idx} of {len(vm_ids)}")

        try:
            # Fetch device data (uses cache helper)
            libre_device = fetch_device_with_cache(vm_id, api, api.server_key, libre_devices_cache)

            if not libre_device:
                result["failed"].append(
                    {
                        "device_id": vm_id,
                        "error": f"Device {vm_id} not found in LibreNMS",
                    }
                )
                log.error(f"Device {vm_id} not found in LibreNMS")
                continue

            # Validate as VM
            use_sysname_opt = sync_options.get("use_sysname", True) if sync_options else True
            strip_domain_opt = sync_options.get("strip_domain", False) if sync_options else False
            validation = validate_device_for_import(
                libre_device,
                import_as_vm=True,
                api=api,
                use_sysname=use_sysname_opt,
                strip_domain=strip_domain_opt,
                server_key=api.server_key,
            )

            # Check if VM already exists
            if validation.get("existing_device"):
                result["skipped"].append(
                    {
                        "device_id": vm_id,
                        "reason": f"VM already exists: {validation['existing_device'].name}",
                    }
                )
                log.info(f"VM already exists: {validation['existing_device'].name}")
                continue

            # Apply manual cluster and role selections
            vm_mappings = vm_imports[vm_id]
            cluster_id = vm_mappings.get("cluster_id")
            role_id = vm_mappings.get("device_role_id")

            if cluster_id:
                cluster = Cluster.objects.filter(id=cluster_id).first()
                if cluster:
                    apply_cluster_to_validation(validation, cluster)
                else:
                    result["failed"].append(
                        {"device_id": vm_id, "error": f"Selected cluster (id={cluster_id}) no longer exists"}
                    )
                    continue

            role = None
            if role_id:
                role = DeviceRole.objects.filter(id=role_id).first()
                if role:
                    apply_role_to_validation(validation, role, is_vm=True)
                else:
                    result["failed"].append(
                        {"device_id": vm_id, "error": f"Selected role (id={role_id}) no longer exists"}
                    )
                    continue

            # Determine VM name
            vm_name = _determine_device_name(
                libre_device,
                use_sysname=use_sysname_opt,
                strip_domain=strip_domain_opt,
                device_id=vm_id,
            )

            # Update validation with computed name
            libre_device["_computed_name"] = vm_name

            # Create VM
            vm = create_vm_from_librenms(
                libre_device,
                validation,
                use_sysname=use_sysname_opt,
                strip_domain=strip_domain_opt,
                server_key=api.server_key,
            )

            result["success"].append(
                {
                    "device_id": vm_id,
                    "device": vm,
                    "message": f"VM {vm.name} created successfully",
                }
            )
            log.info(f"Successfully imported VM {vm.name} (ID: {vm_id})")

        except Exception as vm_error:
            log.error(f"Failed to import VM {vm_id}: {vm_error}", exc_info=True)
            result["failed"].append({"device_id": vm_id, "error": str(vm_error)})

    return result
