from dcim.models import Device, DeviceRole, DeviceType, Site
from ipam.models import Prefix
from dcim.choices import DeviceStatusChoices
from virtualization.choices import VirtualMachineStatusChoices
from virtualization.models import VirtualMachine
from extras.models import Tag
from rest_framework import serializers
import json
import logging
from core.models import Job
from .models import AWXInventory

logger = logging.getLogger(__name__)

group_prefixes = {Site: "site_", DeviceRole: "devicerole_", DeviceType: "devicetype_", Prefix: "prefix_", Tag:"tag_"}


class TagSerializer(serializers.BaseSerializer):
    def to_representation(self, instance):
        return {
            "name": "{}{}".format(group_prefixes[Tag], instance.slug.replace("-", "_")),
            "variables": json.dumps({"netbox_tag_name": instance.name}),
        }

class SiteSerializer(serializers.BaseSerializer):
    def to_representation(self, instance):
        return {
            "name": "{}{}".format(group_prefixes[Site], instance.slug.replace("-", "_")),
            "variables": json.dumps(
                {
                    "netbox_site_status": instance.status,
                }
            ),
        }


class DeviceRoleSerializer(serializers.BaseSerializer):
    def to_representation(self, instance):
        return {
            "name": "{}{}".format(group_prefixes[DeviceRole], instance.slug.replace("-", "_")),
            "variables": json.dumps({})
        }


class DeviceTypeSerializer(serializers.BaseSerializer):
    def to_representation(self, instance):
        return {
            "name": "{}{}".format(group_prefixes[DeviceType], instance.slug.replace("-", "_")),
            "description": instance.description,
            "variables": json.dumps(
                {
                    "netbox_devicetype_model": instance.model,
                }
            ),
        }


class PrefixSerializer(serializers.BaseSerializer):
    def to_representation(self, instance):
        return {
            "name": "{}{}".format(group_prefixes[Prefix], str(instance.prefix).replace(".", "_").replace("/", "_")),
            "variables": json.dumps({"netbox_prefix": "{}".format(str(instance.prefix))}),
        }


class InterfaceSerializer(serializers.BaseSerializer):
    def to_representation(self, instance):
        ip_addresses = []
        for ip in instance.ip_addresses.all():
            serializer = IPAddressSerializer(ip)
            ip_addresses.append(serializer.data)
        return {
            "name": instance.name,
            "mac": instance.mac_address,
            "ip_addresses": ip_addresses
        }


class IPAddressSerializer(serializers.BaseSerializer):
    def to_representation(self, instance):
        return {
            "address": str(instance.address),
            "dns_name": instance.dns_name
        }


class DeviceSerializer(serializers.BaseSerializer):
    def to_representation(self, instance):
        variables = {}
        variables["netbox_interfaces"] = []
        for interface in instance.interfaces.all():
            serializer = InterfaceSerializer(interface)
            variables["netbox_interfaces"].append(serializer.data)
        return {
            "name": getattr(instance.primary_ip4, 'dns_name', instance.name),
            "description": instance.description,
            "enabled": instance.status == DeviceStatusChoices.STATUS_ACTIVE,
            "variables": json.dumps(variables),
        }


class VMInterfaceSerializer(serializers.BaseSerializer):
    def to_representation(self, instance):
        ip_addresses = []
        for ip in instance.ip_addresses.all():
            serializer = IPAddressSerializer(ip)
            ip_addresses.append(serializer.data)
        return {
            "name": instance.name,
            "mac": instance.mac_address,
            "ip_addresses": ip_addresses
        }


class VMSerializer(serializers.BaseSerializer):
    def to_representation(self, instance):
        variables = {
            "netbox_virtualmachine_name": instance.name,
            "netbox_virtualmachine_vcpus": float(instance.vcpus) if instance.vcpus is not None else 0.0,
            "netbox_virtualmachine_memory": instance.memory or 0,
            "netbox_virtualmachine_disk": instance.disk or 0,
        }
        variables["netbox_interfaces"] = []
        for interface in instance.interfaces.all():
            serializer = VMInterfaceSerializer(interface)
            variables["netbox_interfaces"].append(serializer.data)
        return {
            "name": getattr(instance.primary_ip4, 'dns_name', instance.name),
            "description": instance.description,
            "enabled": instance.status == VirtualMachineStatusChoices.STATUS_ACTIVE,
            "variables": json.dumps(variables),
        }


class AWXHostSerializer(serializers.BaseSerializer):
    def to_internal_value(self, data):
        return {
            "name": data.get('name'),
            "description": data.get('description'),
            "enabled": data.get('enabled'),
            "variables": data.get('variables')
        }


class AWXGroupSerializer(serializers.BaseSerializer):
    def to_internal_value(self, data):
        return {
            "name": data.get('name'),
            "variables": data.get('variables')
        }


serializers = {
    Site: SiteSerializer,
    DeviceRole: DeviceRoleSerializer,
    DeviceType: DeviceTypeSerializer,
    Prefix: PrefixSerializer,
    Device: DeviceSerializer,
    VirtualMachine: VMSerializer,
    Tag: TagSerializer
}


def sync_host(inventory, sender, instance):
    serializer = serializers[sender](instance)
    host = inventory.get_host(serializer.data["name"])
    if host is None:
        # If the host doesn't exist, create it.
        inventory.create_host(serializer.data)
        host = inventory.get_host(serializer.data["name"])
    else:
        # If the host exists, update it if necessary.
        host_serializer = AWXHostSerializer(data=host)
        if not host_serializer.is_valid() or host_serializer.validated_data != serializer.data:
            inventory.update_host(host["id"], serializer.data)
    
    # Sync group associations
    current_groups = host["summary_fields"]["groups"]["results"]
    
    # Handle associations for each type (Site, DeviceRole, DeviceType, and Tags)
    if instance.site:
        sync_host_group_association(inventory, host, Site, instance.site, current_groups)
    if instance.role:
        sync_host_group_association(inventory, host, DeviceRole, instance.role, current_groups)
    if isinstance(instance, Device) and instance.device_type:
        sync_host_group_association(inventory, host, DeviceType, instance.device_type, current_groups)
    if tags := instance.tags.all():
        for tag in tags:
            sync_host_group_association(inventory, host, Tag, tag, current_groups)

    # Disassociate groups that are no longer relevant
    disassociate_removed_groups(inventory, host, instance, current_groups)


def sync_host_group_association(inventory, host, object_type, instance, current_groups):
    """
    Associates the host with the appropriate AWX group.
    """
    serializer = serializers[object_type](instance)
    group_name = serializer.data["name"]
    
    # Filter out the groups that match the current object_type (e.g., Site, Tag)
    relevant_groups = [
        group for group in current_groups if group["name"].startswith(group_prefixes[object_type])
    ]
    
    # Check if the host is already in the desired group
    if group_name not in [group["name"] for group in relevant_groups]:
        group = inventory.get_group(group_name)
        if group:
            inventory.associate_host_group(host["id"], group["id"])
            logger.info(f"Host {host['name']} associated with group {group_name}.")


def disassociate_removed_groups(inventory, host, instance, current_groups):
    """
    Disassociates the host from AWX groups that are no longer relevant.
    """
    # Collect the current groups that should remain (based on the instance's attributes)
    valid_group_names = set()
    
    if instance.site:
        valid_group_names.add(f"{group_prefixes[Site]}{instance.site.slug.replace('-', '_')}")
    if instance.role:
        valid_group_names.add(f"{group_prefixes[DeviceRole]}{instance.role.slug.replace('-', '_')}")
    if isinstance(instance, Device) and instance.device_type:
        valid_group_names.add(f"{group_prefixes[DeviceType]}{instance.device_type.slug.replace('-', '_')}")
    if tags := instance.tags.all():
        valid_group_names.update(
            f"{group_prefixes[Tag]}{tag.slug.replace('-', '_')}" for tag in tags
        )
    # Disassociate from groups that are no longer valid
    for group in current_groups:
        if group["name"] not in valid_group_names:
            inventory.disassociate_host_group(host["id"], group["id"])
            logger.info(f"Host {host['name']} disassociated from group {group['name']}.")

def delete_host(inventory, sender, instance):
    serializer = serializers[sender](instance)
    inventory.delete_host(serializer.data["name"])


def sync_group(inventory, sender, instance):
    serializer = serializers[sender](instance)
    group = inventory.get_group(serializer.data["name"])
    if group is None:
        inventory.create_group(serializer.data)
        group = inventory.get_group(serializer.data["name"])
    else:
        group_serializer = AWXGroupSerializer(data=group)
        if not group_serializer.is_valid() or group_serializer.validated_data != serializer.data:
            print("Group will be updated due to:")
            print(group_serializer.validated_data)
            print(serializer.data)
            inventory.update_group(serializer.data["name"], serializer.data)


def delete_group(inventory, sender, instance):
    serializer = serializers[sender](instance)
    inventory.delete_group(serializer.data["name"])


def sync_all(job):
    inventory = job.object
    logger.info("Performing full inventory sync for inventory {}".format(inventory.inventory_id))
    job.start()
    for site in Site.objects.all():
        sync_group(inventory, Site, site)
    for device_role in DeviceRole.objects.all():
        sync_group(inventory, DeviceRole, device_role)
    for device_type in DeviceType.objects.all():
        sync_group(inventory, DeviceType, device_type)
    for tag in Tag.objects.all():
        sync_group(inventory, Tag, tag)

    for device in Device.objects.all():
        if not device.primary_ip4 is None and device.primary_ip4.dns_name:
            sync_host(inventory, Device, device)
    for vm in VirtualMachine.objects.all():
        if not vm.primary_ip4 is None and vm.primary_ip4.dns_name:
            sync_host(inventory, VirtualMachine, vm)
    job.terminate()