from typing import TYPE_CHECKING

from django.db import IntegrityError
from django.db import migrations
from django.utils.text import slugify

from ipfabric_netbox.utilities.transform_map import do_change
from ipfabric_netbox.utilities.transform_map import FieldRecord
from ipfabric_netbox.utilities.transform_map import RelationshipRecord
from ipfabric_netbox.utilities.transform_map import TransformMapRecord

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

TRANSFORM_MAP_CHANGES = (
    # Platform Transform Map changes
    TransformMapRecord(
        source_endpoint="/inventory/devices",
        target_model="dcim.platform",
        fields=(
            FieldRecord(
                source_field="vendor",
                target_field="slug",
                old_template="{{ object.vendor | slugify }}{%if object.family %}_{{ object.family | slugify }}{% endif %}",
                new_template="{% if object.family %}{{ object.family | slugify }}{% else %}{{ object.vendor | slugify }}{% endif %}",
            ),
            FieldRecord(
                source_field="family",
                target_field="name",
                old_template="{%if object.family %}{{ object.family | slugify }}{% else %}{{ object.vendor }}{% endif %}",
                new_template="{% if object.family %}{{ object.family }}{% else %}{{ object.vendor }}{% endif %}",
            ),
        ),
        relationships=(
            RelationshipRecord(
                source_model="dcim.manufacturer",
                target_field="manufacturer",
                coalesce=True,
            ),
        ),
    ),
    # Device Transform Map changes - platform relationship
    TransformMapRecord(
        source_endpoint="/inventory/devices",
        target_model="dcim.device",
        relationships=(
            RelationshipRecord(
                source_model="dcim.platform",
                target_field="platform",
                old_template='{% set SLUG = object.vendor | slugify %}{% if object.family %}{% set SLUG = SLUG ~ "_" ~ object.family | slugify %}{% endif %}\n{{ dcim.Platform.objects.get(slug=SLUG).pk }}',
                new_template="{% set MANUFACTURER_SLUG = object.vendor | slugify %}{% if object.family %}{% set SLUG = object.family | slugify %}{% else %}{% set SLUG = object.vendor | slugify %}{% endif %}{{ dcim.Platform.objects.get(slug=SLUG, manufacturer__slug=MANUFACTURER_SLUG).pk }}",
            ),
        ),
    ),
)


def forward_transform_maps_change(
    apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
):
    """Replace old template with updated version."""
    do_change(apps, schema_editor, changes=TRANSFORM_MAP_CHANGES, forward=True)


def revert_transform_maps_change(
    apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
):
    """Revert template back to the previous exact template."""
    do_change(apps, schema_editor, changes=TRANSFORM_MAP_CHANGES, forward=False)


def cleanup_platform_slugs_forward(
    apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
):
    """
    Migrate existing Platform records by regenerating slug from name field.

    Always regenerates slug from the name field using slugify().
    New format: family OR vendor (whatever is in the name field)

    Examples:
    - name="IOS" -> slug="ios"
    - name="JunOS" -> slug="junos"
    - name="Palo Alto" -> slug="palo-alto"
    - name="Cisco" -> slug="cisco"

    Handles conflicts gracefully by skipping platforms that would create duplicates.
    """

    Platform = apps.get_model("dcim", "Platform")

    skipped_platforms = []
    updated_count = 0
    skipped_no_change = 0

    platforms = Platform.objects.using(schema_editor.connection.alias).all()

    for platform in platforms:
        old_slug = platform.slug

        # Always regenerate slug from name field
        # The name field contains either family or vendor (based on template logic)
        new_slug = slugify(platform.name)

        # If the slug is already correct, no change needed
        if old_slug == new_slug:
            skipped_no_change += 1
            continue

        try:
            # Update the slug
            platform.slug = new_slug
            platform.save(using=schema_editor.connection.alias)
            updated_count += 1
        except IntegrityError:
            # Slug already exists, skip this platform
            skipped_platforms.append(
                {
                    "old_slug": old_slug,
                    "attempted_new_slug": new_slug,
                    "name": platform.name,
                }
            )

    if skipped_platforms:
        print(
            f"Platform slug cleanup: Updated {updated_count} platforms, "
            f"skipped {len(skipped_platforms)} due to conflicts, "
            f"{skipped_no_change} unchanged: "
        )
        for skipped in skipped_platforms:
            print(
                f" - '{skipped['old_slug']}' -> '{skipped['attempted_new_slug']}' "
                f"(name: {skipped['name']}) - slug already exists"
            )
    else:
        print(
            f"Platform slug cleanup: Updated {updated_count} platforms successfully, "
            f"{skipped_no_change} unchanged"
        )


class Migration(migrations.Migration):
    dependencies = [
        ("ipfabric_netbox", "0025_add_vss_chassis_endpoint"),
    ]

    operations = [
        migrations.RunPython(
            forward_transform_maps_change,
            revert_transform_maps_change,
        ),
        migrations.RunPython(
            cleanup_platform_slugs_forward,
            # We cannot know which platforms were changed, so no-op on reverse
            migrations.RunPython.noop,
        ),
    ]
