from . import base_name, plugin_type
from .imports import *

from .. import get_config
from ..models import  SlurpitStagedDevice, ensure_slurpit_tags
from ..utilities import format_time_ticks

from django.utils.text import slugify

def create_form(form, data, model, initial):
    """Create a form instance with the provided data and initial values.
    
    Args:
        form: Form class to instantiate
        data: POST data dictionary
        model: Model class (unused, kept for compatibility)
        initial: Initial values dictionary
    
    Returns:
        Form instance
    """
    return form(data, initial=initial)

def get_form_device_data(form):
    """Extract device-related data from a validated form.
    
    Args:
        form: Validated form instance with cleaned_data
    
    Returns:
        Dictionary containing device attributes: role, status, location, rack,
        position, latitude, longitude, tenant, description, airflow, interface_name
    """
    return {
            'role': form.cleaned_data['role'],
            'status': form.cleaned_data['status'],
            'location': form.cleaned_data['location'],
            'rack': form.cleaned_data['rack'],
            'position': form.cleaned_data['position'],
            'latitude': form.cleaned_data['latitude'],
            'longitude': form.cleaned_data['longitude'],
            'tenant': form.cleaned_data['tenant'],
            'description': form.cleaned_data['description'],
            'airflow': form.cleaned_data['airflow'],
            'interface_name': form.cleaned_data['interface_name']
        }

def set_device_custom_fields(device, fields):
    """Set custom fields on a device object.
    
    Args:
        device: Device instance to update
        fields: Dictionary of custom field names and values to set
    """
    for k, v in fields.items():
        device.custom_field_data[k] = v

def get_default_objects():
    """Get default DeviceType, DeviceRole, and Site objects from plugin configuration.
    
    Returns:
        Dictionary with keys 'device_type', 'role', 'site' containing the default
        objects, or empty dict if not found
    """
    device_type = DeviceType.objects.filter(**get_config('DeviceType'))
    role = DeviceRole.objects.filter(**get_config('DeviceRole'))
    site = Site.objects.filter(**get_config('Site'))
    defaults = {}
    if device_type:
        defaults['device_type'] = device_type.first()
    if role:
        defaults['role'] = role.first()
    if site:
        defaults['site'] = site.first()

    return defaults

def status_inventory():
    """Return the inventory status choice value.
    
    Returns:
        DeviceStatusChoices.STATUS_INVENTORY
    """
    return DeviceStatusChoices.STATUS_INVENTORY

def status_active():
    """Return the active status choice value.
    
    Returns:
        DeviceStatusChoices.STATUS_ACTIVE
    """
    return DeviceStatusChoices.STATUS_ACTIVE

def status_offline():
    """Return the offline status choice value.
    
    Returns:
        DeviceStatusChoices.STATUS_OFFLINE
    """
    return DeviceStatusChoices.STATUS_OFFLINE

def status_decommissioning():
    """Return the decommissioning status choice value.
    
    Returns:
        DeviceStatusChoices.STATUS_DECOMMISSIONING
    """
    return DeviceStatusChoices.STATUS_DECOMMISSIONING

def get_create_dcim_objects(staged):
    """Create or get Manufacturer, Platform, and DeviceType objects from staged device data.
    
    Creates Manufacturer from staged.brand, Platform from staged.device_os, and DeviceType
    from staged.device_type. Applies Slurp'it tags to newly created objects.
    
    Args:
        staged: SlurpitStagedDevice instance containing device metadata
    
    Returns:
        DeviceType object (created or existing)
    """
    try:
        manu, new = Manufacturer.objects.get_or_create(name=staged.brand, defaults={'slug':slugify(staged.brand)})
    except:
        manu, new = Manufacturer.objects.get_or_create(slug=slugify(staged.brand), defaults={'name':staged.brand})
    
    if new:
        ensure_slurpit_tags(manu)
    
    try:
        platform, new = Platform.objects.get_or_create(name=staged.device_os, defaults={'slug':slugify(staged.device_os)})
    except:
        platform, new = Platform.objects.get_or_create(slug=slugify(staged.device_os), defaults={'name':staged.device_os})
    try:
        dtype, new = DeviceType.objects.get_or_create(
            model=staged.device_type, 
            manufacturer=manu, 
            defaults={
                'slug':slugify(f'{staged.brand}-{staged.device_type}'), 
                'default_platform':platform
            }
        )
    except:
        dtype, new = DeviceType.objects.get_or_create(
            slug=slugify(f'{staged.brand}-{staged.device_type}'), 
            manufacturer=manu, 
            defaults={
                'model':staged.device_type, 
                'default_platform':platform
            }
        )
    if new:
        ensure_slurpit_tags(dtype)
    return dtype

class SlurpitViewMixim:
    """Mixin class that provides common context data for Slurp'it views.
    
    Adds plugin metadata to view context including plugin type, base name, and version.
    Subclasses can override slurpit_extra_context() to add additional context.
    """
    slurpit_data = {
            'plugin_type': plugin_type,
            'base_name': base_name,
            'plugin_base_name': f"plugins:{base_name}",
            'version': get_config('version'),
    }
    
    def get_extra_context(self, request):
        """Get extra context data for the view.
        
        Args:
            request: HTTP request object
        
        Returns:
            Dictionary combining slurpit_data and slurpit_extra_context()
        """
        return {**self.slurpit_data, **self.slurpit_extra_context()}
    
    def slurpit_extra_context(self):
        """Override this method to add additional context data.
        
        Returns:
            Dictionary of additional context (empty by default)
        """
        return {}

class SlurpitViewSet(NetBoxModelViewSet):
    """Base viewset for Slurp'it API views.
    
    Extends NetBoxModelViewSet to provide standard CRUD operations for Slurp'it models.
    """
    pass

class SlurpitQuerySetMixim:
    """Mixin for custom queryset operations.
    
    Placeholder for future queryset customization functionality.
    """
    pass

def get_management_ip(obj):
    """Get management IP address from object (checks both ipv4 and address attributes)."""
    return getattr(obj, "ipv4", None) or getattr(obj, "address", None)

def get_device_custom_fields_dict(obj):
    """Build dictionary of device custom fields from SlurpitImportedDevice object.
    
    Formats SNMP uptime values into human-readable format before storing in custom fields.
    
    Returns:
        Dictionary with custom field names as keys and values from the object.
        SNMP uptime is formatted as "X days, HH:MM:SS" if valid, otherwise original value.
    """
    # Format SNMP uptime if available
    snmp_uptime_value = obj.snmp_uptime
    formatted_uptime = format_time_ticks(snmp_uptime_value)
    if formatted_uptime:
        snmp_uptime_value = formatted_uptime
    
    return {
        'slurpit_hostname': obj.hostname,
        'slurpit_fqdn': obj.fqdn,
        'slurpit_platform': obj.device_os,
        'slurpit_manufacturer': obj.brand,
        'slurpit_devicetype': obj.device_type,
        'slurpit_ipv4': get_management_ip(obj),
        'slurpit_site': obj.site,
        'slurpit_serial': obj.serial,
        'slurpit_os_version': obj.os_version,
        'slurpit_snmp_uptime': snmp_uptime_value
    }

def update_device_site(device, site_name):
    """Update device site, using default if site_name is None or empty.
    
    Args:
        device: Device instance to update
        site_name: Name of the site to assign, or None/empty string to use default
    """
    defaults = get_default_objects()
    site = defaults['site']
    if site_name is not None and site_name != "":
        site = Site.objects.get(name=site_name)
    device.site = site

def assign_device_platform(device, device_os):
    """Assign platform to device, handling exceptions and device type defaults.
    
    If device has a device_type, uses its default_platform. Otherwise, attempts to
    find Platform by name or slug.
    
    Args:
        device: Device instance to update
        device_os: Operating system name to match against Platform
    """
    if device.device_type:
        device.platform = device.device_type.default_platform
    else:
        try:
            device.platform = Platform.objects.get(name=device_os)
        except:
            device.platform = Platform.objects.get(slug=slugify(device_os))

def assign_primary_ip_address(device, mgmt_ip, get_ip_device_func):
    """Handle primary IP address assignment for a device.
    
    Args:
        device: Device object
        mgmt_ip: Management IP address string
        get_ip_device_func: Function to get/create IPAddress object
    """
    if not mgmt_ip:
        return
    
    # Remove Primary IPv4 on other device
    other_device = Device.objects.filter(primary_ip4__address__net_host=mgmt_ip).exclude(id=device.id).first()
    if other_device:
        other_device.primary_ip4 = None
        other_device.save()
    
    ipaddress = get_ip_device_func(mgmt_ip, device)
    if device.primary_ip4 != ipaddress:
        device.primary_ip4 = ipaddress
        device.save()

def normalize_ip_address(address, prefix_model=None):
    """Normalize IP address by adding CIDR notation if missing.
    
    Args:
        address: IP address string (may or may not include CIDR)
        prefix_model: Prefix model class to search for matching prefix (optional)
    
    Returns:
        Normalized IP address string with CIDR notation
    """
    if not address:
        return address
    
    # Remove /32 suffix if present
    if address.endswith("/32"):
        address = address[:-3]
    
    # If already has CIDR notation, return as is
    if '/' in address:
        return address
    
    # Try to find matching prefix
    if prefix_model:
        prefix = prefix_model.objects.filter(prefix__net_contains=address).order_by('-prefix').first()
        if prefix:
            return f'{address}/{prefix.prefix.prefixlen}'
    
    # Default to /32 if no prefix found
    return f'{address}/32'

def get_or_create_vrf(vrf_name):
    """Get or create VRF by name.
    
    Args:
        vrf_name: VRF name string
    
    Returns:
        VRF object or None if vrf_name is empty/None
    """
    from ipam.models import VRF
    
    if not vrf_name or len(vrf_name) == 0:
        return None
    
    vrf = VRF.objects.filter(name=vrf_name).first()
    if not vrf:
        vrf = VRF.objects.create(name=vrf_name)
    return vrf

def batch_process(items, batch_size, process_func):
    """Process items in batches.
    
    Args:
        items: List of items to process
        batch_size: Size of each batch
        process_func: Function to process each batch (receives batch list)
    
    Example:
        batch_process(items, 128, lambda batch: Model.objects.bulk_create(batch))
    """
    count = len(items)
    offset = 0
    while offset < count:
        batch = items[offset:offset + batch_size]
        process_func(batch)
        offset += batch_size

def remove_duplicates(model_class, unique_fields):
    """Remove duplicate records, keeping only the first occurrence.
    
    Args:
        model_class: Django model class
        unique_fields: List of field names that should be unique together
    
    Example:
        remove_duplicates(SlurpitInterface, ['name', 'device_id'])
    """
    from django.db.models import Count
    duplicates = model_class.objects.values(*unique_fields).annotate(count=Count('id')).filter(count__gt=1)
    for duplicate in duplicates:
        records = model_class.objects.filter(**{field: duplicate[field] for field in unique_fields})
        records.exclude(id=records.first().id).delete()