import importlib
import inspect
import os
import sys
from importlib import import_module

from django.apps import apps
from django.core.management import call_command
from django.core.management.base import BaseCommand
from django.template import Context, Template

from flexi_tag.utils.models import FlexiTagMixin


class Command(BaseCommand):
    help = "Generate tag models for all models that inherit from FlexiTagMixin"

    def add_arguments(self, parser):
        parser.add_argument(
            "--dry-run",
            action="store_true",
            help="Show what would be generated without creating files",
        )
        parser.add_argument(
            "--force-reload",
            action="store_true",
            help="Force reload of all app models to detect recent changes",
        )

    def handle(self, **options):
        dry_run = options.get("dry_run", False)
        force_reload = options.get("force_reload", False)

        # Clear Django's app registry cache to ensure we detect new FlexiTagMixin models
        self.stdout.write("Detecting FlexiTagMixin models...")
        apps.clear_cache()

        if force_reload:
            self.stdout.write("Force reloading models modules...")
            for app_config in apps.get_app_configs():
                if hasattr(app_config, "models_module") and app_config.models_module:
                    try:
                        # Force reload the models module
                        importlib.reload(app_config.models_module)
                    except (ImportError, AttributeError):
                        # Some apps might not have models, that's OK
                        pass

        # Group models by app
        models_by_app = {}

        for app_config in apps.get_app_configs():
            app_models = []
            for model in app_config.get_models():
                if issubclass(model, FlexiTagMixin) and model is not FlexiTagMixin:
                    app_models.append(model)

            if app_models:
                models_by_app[app_config] = app_models

        # Template for the generated model file (supports multiple models)
        model_template = Template(
            """
# This file is auto-generated by the generate_tag_models command via "flexi-tag" package.
# Do not edit manually.

from django.db import models
from flexi_tag.utils.compat import JSONField, GinIndex

{% for model_data in models %}
class {{ model_data.model_name }}Tag(models.Model):
    instance = models.OneToOneField(
        "{{ model_data.app_label }}.{{ model_data.model_name }}",
        on_delete=models.CASCADE,
        primary_key=True,
    )
    tags = JSONField(default=list)

    class Meta:
        indexes = [GinIndex(fields=["tags"])]

    def __str__(self):
        return "Tags for {}".format(self.instance)

{% endfor %}
"""
        )

        for app_config, models in models_by_app.items():
            # Prepare context data for all models in this app
            models_data = []
            tag_model_names = []

            for model in models:
                model_name = model.__name__
                app_label = model._meta.app_label

                models_data.append(
                    {
                        "model_name": model_name,
                        "app_label": app_label,
                        "model_lower_name": model_name.lower(),
                    }
                )
                tag_model_names.append("{}Tag".format(model_name))

            context = Context({"models": models_data})
            model_content = model_template.render(context)

            # Get the directory for this app (using the first model to determine location)
            first_model = models[0]
            model_module = import_module(first_model.__module__)
            model_dir = os.path.dirname(inspect.getfile(model_module))
            file_path = os.path.join(model_dir, "flexi_generated_model.py")

            if dry_run:
                self.stdout.write("Would create file: {}".format(file_path))
                self.stdout.write(model_content)
                self.stdout.write("-" * 40)
            else:
                # Write all models for this app in one operation
                with open(file_path, "w") as f:
                    f.write(model_content)

                # Update imports in models.py
                models_file_path = os.path.join(model_dir, "models.py")
                if os.path.exists(models_file_path):
                    self._update_imports(models_file_path, tag_model_names)

                for model_data in models_data:
                    self.stdout.write(
                        self.style.SUCCESS(
                            "Created tag model for {app_label}.{model_name} at {file_path}".format(
                                app_label=model_data["app_label"],
                                model_name=model_data["model_name"],
                                file_path=file_path,
                            )
                        )
                    )

        try:
            from django.contrib.postgres.indexes import GinIndex  # noqa
        except ImportError:
            self.stdout.write(
                self.style.WARNING(
                    "Important Note: GinIndex is not available. You need to add it manually with migrations."
                )
            )

        if dry_run:
            self.stdout.write("Dry run completed. No files were created.")
        else:
            self.stdout.write(
                self.style.SUCCESS("Successfully generated all tag models!")
            )
            is_test = (
                "test" in sys.argv
                or os.environ.get("DJANGO_SETTINGS_MODULE")
                == "flexi_tag.tests.test_settings"
            )

            if not is_test:
                self.stdout.write("Running makemigrations command...")
                call_command("makemigrations")
                self.stdout.write(
                    self.style.SUCCESS("Successfully created migrations!")
                )
            else:
                self.stdout.write("Skipping makemigrations in test environment.")

    def _update_imports(self, models_file_path, tag_model_names):
        """Update the imports in models.py to include all tag models."""
        with open(models_file_path, "r") as f:
            models_content = f.read()

        import_pattern = "from .flexi_generated_model import"
        lines = models_content.splitlines()
        existing_import_line = None
        last_import_index = -1

        # Find existing import line and last import line
        for i, line in enumerate(lines):
            if line.startswith(import_pattern):
                existing_import_line = line
                last_import_index = i
            elif line.startswith("import ") or line.startswith("from "):
                if existing_import_line is None:
                    last_import_index = i

        if existing_import_line:
            # Replace existing import with all tag models
            import_line = "{import_pattern} {tag_model_names}  # noqa".format(
                import_pattern=import_pattern,
                tag_model_names=", ".join(tag_model_names),
            )
            lines[last_import_index] = import_line
        else:
            # Add new import after last import
            if last_import_index >= 0:
                import_line = "\n\nfrom .flexi_generated_model import {tag_model_names}  # noqa".format(
                    tag_model_names=", ".join(tag_model_names)
                )
                lines.insert(last_import_index + 1, import_line)

        updated_content = "\n".join(lines)
        with open(models_file_path, "w") as f:
            f.write(updated_content)
