# Generated by Django 5.1.6 on 2025-04-24 11:46
import contextlib
from enum import IntEnum
from typing import TYPE_CHECKING

import django.db.models.deletion
from django.db import migrations
from django.db import models

if TYPE_CHECKING:
    from django.apps import apps as apps_type
    from django.db.backends.base.schema import BaseDatabaseSchemaEditor


def modify_custom_field(
    apps: "apps_type",
    current_name: str,
    new_name: str,
    new_label: str,
    new_object_type: models.Model,
):
    CustomField = apps.get_model("extras", "CustomField")
    ObjectType = apps.get_model("core", "ObjectType")

    try:
        custom_field = CustomField.objects.get(name=current_name)
    except CustomField.DoesNotExist:
        return

    custom_field.name = new_name
    custom_field.label = new_label
    custom_field.related_object_type = ObjectType.objects.get_for_model(new_object_type)
    custom_field.full_clean()
    custom_field.save()


def rename_ipfabric_branch_cf(
    apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
):
    """Forward migration to rename ipfabric_branch CustomField to ipfabric_ingestion."""
    modify_custom_field(
        apps,
        "ipfabric_branch",
        "ipfabric_ingestion",
        "IP Fabric Last Ingestion",
        apps.get_model("ipfabric_netbox", "IPFabricIngestion"),
    )


def rename_ipfabric_ingestion_cf(
    apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
):
    """Forward migration to rename ipfabric_ingestion CustomField to ipfabric_branch."""
    modify_custom_field(
        apps,
        "ipfabric_ingestion",
        "ipfabric_branch",
        "IP Fabric Last Sync",
        apps.get_model("ipfabric_netbox", "IPFabricBranch"),
    )


def clean_redisual_branch_data(
    apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
):
    """
    Clean up any residual data in the Branch model that have been left behind
    after the migration.
    """
    ContentType = apps.get_model("contenttypes", "contenttype")
    Permission = apps.get_model("auth", "permission")

    try:
        branch_content_type = ContentType.objects.get(
            app_label="ipfabric_netbox", model="ipfabricbranch"
        )
    except ContentType.DoesNotExist:
        return

    Permission.objects.filter(content_type=branch_content_type).delete()
    branch_content_type.delete()


# interface fix templates
DUPLEX_TEMPLATE_BEFORE = ""
DUPLEX_TEMPLATE_AFTER = '{% if not object.duplex or object.duplex=="unknown" %}None{% else %}{{ object.duplex }}{% endif %}'
SPEED_TEMPLATE_BEFORE = "{% if not object.speedValue %}None{% else %}{{ object.speedValue|int // 1000 }}{% endif %}"
SPEED_TEMPLATE_AFTER = '{% if not object.speedValue or object.speedValue=="unknown" %}None{% else %}{{ object.speedValue|int // 1000 }}{% endif %}'

# devicetype fix templates
DEVICETYPE_RELATIONSHIP_TEMPLATE_BEFORE = '{% if object.model != none %}{% set SLUG = object.model | string | slugify %}{% else %}{% set SLUG = object.vendor | slugify ~ "-" ~ object.family | slugify ~ "-" ~ object.platform %}{% endif %}{{ dcim.DeviceType.objects.get(slug=SLUG).pk }}'
DEVICETYPE_RELATIONSHIP_TEMPLATE_AFTER = '{% if object.model %}{% set SLUG = object.model | string | slugify %}{% else %}{% set SLUG = object.vendor | slugify ~ "-" ~ object.family | slugify ~ "-" ~ object.platform | slugify %}{% endif %}{{ dcim.DeviceType.objects.get(slug=SLUG).pk }}'
MODEL_SLUG_TEMPLATE_BEFORE = "{% if object.model != none %}{{ object.model | string | slugify }}{% else %}{{ object.vendor | slugify }}-{{ object.family | slugify}}-{{ object.platform }}{% endif %}"
MODEL_SLUG_TEMPLATE_AFTER = "{% if object.model %}{{ object.model | string | slugify }}{% else %}{{ object.vendor | slugify }}-{{ object.family | slugify}}-{{ object.platform | slugify }}{% endif %}"
MODEL_TEMPLATE_BEFORE = "{% if object.model != none %}{{ object.model | string }}{% else %}{{ object.vendor }} - {{ object.family }} - {{ object.platform }}{% endif %}"
MODEL_TEMPLATE_AFTER = "{% if object.model %}{{ object.model | string }}{% else %}{{ object.vendor }} - {{ object.family }} - {{ object.platform }}{% endif %}"

TEMPLATES = {
    "duplex": (DUPLEX_TEMPLATE_BEFORE, DUPLEX_TEMPLATE_AFTER),
    "speed": (SPEED_TEMPLATE_BEFORE, SPEED_TEMPLATE_AFTER),
    "devicetype_relationship": (
        DEVICETYPE_RELATIONSHIP_TEMPLATE_BEFORE,
        DEVICETYPE_RELATIONSHIP_TEMPLATE_AFTER,
    ),
    "model_slug": (MODEL_SLUG_TEMPLATE_BEFORE, MODEL_SLUG_TEMPLATE_AFTER),
    "model": (MODEL_TEMPLATE_BEFORE, MODEL_TEMPLATE_AFTER),
}


class MigrationType(IntEnum):
    FORWARD = 0
    REVERSE = 1


def change_transform_maps(
    apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor", migration_type: int
):
    """
    * Ignore 'unknown' values for duplex and speed on Interface.
    * Correctly set DeviceType value when model is empty (not None!).
    * Slugify platform when using it for DeviceType slug.
    """
    ContentType = apps.get_model("contenttypes", "ContentType")
    TransformMap = apps.get_model("ipfabric_netbox", "IPFabricTransformMap")
    TransformField = apps.get_model("ipfabric_netbox", "IPFabricTransformField")
    RelationshipField = apps.get_model("ipfabric_netbox", "IPFabricRelationshipField")

    with contextlib.suppress(TransformMap.DoesNotExist, TransformField.DoesNotExist):
        interface_transform_map = TransformMap.objects.get(
            source_model="interface",
            target_model__app_label="dcim",
            target_model__model="interface",
        )
        TransformField.objects.filter(
            transform_map=interface_transform_map,
            template=TEMPLATES["duplex"][migration_type],
            source_field="duplex",
            target_field="duplex",
        ).update(template=TEMPLATES["duplex"][int(not migration_type)])
        TransformField.objects.filter(
            transform_map=interface_transform_map,
            template=TEMPLATES["speed"][migration_type],
            source_field="speedValue",
            target_field="speed",
        ).update(template=TEMPLATES["speed"][int(not migration_type)])

    with contextlib.suppress(
        TransformMap.DoesNotExist,
        TransformField.DoesNotExist,
        RelationshipField.DoesNotExist,
    ):
        device_transform_map = TransformMap.objects.get(
            source_model="device",
            target_model__app_label="dcim",
            target_model__model="device",
        )
        RelationshipField.objects.filter(
            transform_map=device_transform_map,
            template=TEMPLATES["devicetype_relationship"][migration_type],
            source_model=ContentType.objects.get(app_label="dcim", model="devicetype"),
            target_field="device_type",
        ).update(template=TEMPLATES["devicetype_relationship"][int(not migration_type)])

        devicetype_transform_map = TransformMap.objects.get(
            source_model="device",
            target_model__app_label="dcim",
            target_model__model="devicetype",
        )
        TransformField.objects.filter(
            transform_map=devicetype_transform_map,
            template=TEMPLATES["model_slug"][migration_type],
            source_field="model",
            target_field="slug",
        ).update(template=TEMPLATES["model_slug"][int(not migration_type)])
        TransformField.objects.filter(
            transform_map=devicetype_transform_map,
            template=TEMPLATES["model"][migration_type],
            source_field="model",
            target_field="model",
        ).update(template=TEMPLATES["model"][int(not migration_type)])


def forward_change_transform_maps(
    apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
):
    """
    * Ignore 'unknown' values for duplex and speed on Interface.
    * Correctly set DeviceType value when model is empty (not None!).
    * Slugify platform when using it for DeviceType slug.
    """
    change_transform_maps(apps, schema_editor, migration_type=MigrationType.FORWARD)


def reverse_change_transform_maps(
    apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
):
    """
    Return changes done in `change_transform_maps` to the original state.
    """
    change_transform_maps(apps, schema_editor, migration_type=MigrationType.REVERSE)


class Migration(migrations.Migration):
    dependencies = [
        ("core", "0012_job_object_type_optional"),
        ("ipfabric_netbox", "0012_remove_status_field"),
        ("netbox_branching", "0003_rename_indexes"),
        ("extras", "0123_journalentry_kind_default"),
    ]

    operations = [
        migrations.RunPython(migrations.RunPython.noop, rename_ipfabric_ingestion_cf),
        migrations.DeleteModel(
            name="IPFabricBranch",
        ),
        migrations.CreateModel(
            name="IPFabricIngestion",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True, primary_key=True, serialize=False
                    ),
                ),
                (
                    "branch",
                    models.OneToOneField(
                        null=True,
                        on_delete=django.db.models.deletion.SET_NULL,
                        to="netbox_branching.branch",
                    ),
                ),
                (
                    "job",
                    models.ForeignKey(
                        null=True,
                        on_delete=django.db.models.deletion.SET_NULL,
                        to="core.job",
                    ),
                ),
                (
                    "sync",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.CASCADE,
                        to="ipfabric_netbox.ipfabricsync",
                    ),
                ),
            ],
            options={
                "verbose_name": "IP Fabric Ingestion",
                "verbose_name_plural": "IP Fabric Ingestion",
                "ordering": ("pk",),
            },
        ),
        migrations.AlterField(
            model_name="ipfabricrelationshipfield",
            name="template",
            field=models.TextField(blank=True, default=""),
        ),
        migrations.AlterField(
            model_name="ipfabrictransformfield",
            name="template",
            field=models.TextField(blank=True, default=""),
        ),
        migrations.RunPython(rename_ipfabric_branch_cf, migrations.RunPython.noop),
        migrations.RunPython(clean_redisual_branch_data, migrations.RunPython.noop),
        migrations.RunPython(
            forward_change_transform_maps, reverse_change_transform_maps
        ),
    ]
