import os
import shutil
import tempfile
from io import StringIO  # Python 3
from unittest import mock

from django.core.management import call_command
from django.template import Context, Engine, Template
from django.test import TestCase

from flexi_tag.management.commands.generate_tag_models import Command
from flexi_tag.utils.models import FlexiTagMixin


class TagModelGenerationTestCase(TestCase):
    def setUp(self):
        self.test_dir = tempfile.mkdtemp()

        self.model_template_string = """
# This file is auto-generated. Do not edit manually.
# Generated by the generate_tag_models command.

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


class {{ model_name }}Tag(models.Model):
    instance = models.OneToOneField(
        "{{ app_label }}.{{ 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)"
"""

    def tearDown(self):
        shutil.rmtree(self.test_dir)

    def _create_mock_model(
        self, name="MyModel", app_label="myapp", module="myapp.models"
    ):
        mock_model = mock.MagicMock()
        mock_model.__name__ = name
        mock_model._meta.app_label = app_label
        mock_model.__module__ = module
        mock_model.__bases__ = (FlexiTagMixin,)
        return mock_model


class GenerateTagModelsCommandTestCase(TagModelGenerationTestCase):
    @mock.patch("django.template.Engine.get_default")
    def test_template_content(self, mock_get_default):
        engine = Engine()
        mock_get_default.return_value = engine

        template = Template(self.model_template_string)

        context = Context(
            {
                "model_name": "TaggableManagerTestModel",
                "app_label": "testapp",
                "model_lower_name": "taggablemanagertestmodel",
            }
        )

        rendered_template = template.render(context)

        self.assertIn(
            "class TaggableManagerTestModelTag(models.Model):", rendered_template
        )

    @mock.patch(
        "flexi_tag.management.commands.generate_tag_models.apps.get_app_configs"
    )
    @mock.patch("flexi_tag.management.commands.generate_tag_models.Template")
    def test_command_with_no_flexi_tag_models(
        self, mock_template, mock_get_app_configs
    ):
        mock_template_instance = mock.MagicMock()
        mock_template_instance.render.return_value = "# Generated model content"
        mock_template.return_value = mock_template_instance

        out = StringIO()

        mock_model = mock.MagicMock()
        mock_model.__name__ = "RegularModel"
        mock_model.__bases__ = (object,)  # Not a FlexiTagMixin

        mock_app_config = mock.MagicMock()
        mock_app_config.get_models.return_value = [mock_model]
        mock_get_app_configs.return_value = [mock_app_config]

        call_command("generate_tag_models", stdout=out)

        self.assertIn("Successfully generated all tag models", out.getvalue())

    @mock.patch("flexi_tag.management.commands.generate_tag_models.Template")
    @mock.patch(
        "flexi_tag.management.commands.generate_tag_models.apps.get_app_configs"
    )
    @mock.patch("flexi_tag.management.commands.generate_tag_models.import_module")
    @mock.patch("flexi_tag.management.commands.generate_tag_models.inspect.getfile")
    def test_dry_run_option(
        self,
        mock_getfile,
        mock_import_module,
        mock_get_app_configs,
        mock_template_class,
    ):
        mock_template = mock.MagicMock()
        mock_template.render.return_value = "# Mocked template content"
        mock_template_class.return_value = mock_template

        mock_model = self._create_mock_model(
            name="TaggableManagerTestModel",
            app_label="testapp",
            module="testapp.models",
        )

        mock_app_config = mock.MagicMock()
        mock_app_config.get_models.return_value = [mock_model]
        mock_get_app_configs.return_value = [mock_app_config]

        mock_module = mock.MagicMock()
        mock_import_module.return_value = mock_module
        test_file_path = os.path.join(self.test_dir, "models.py")
        mock_getfile.return_value = test_file_path

        # Run the command with --dry-run
        out = StringIO()
        call_command("generate_tag_models", "--dry-run", stdout=out)

        output = out.getvalue()
        self.assertIn("Would create file:", output)
        self.assertIn("Dry run completed. No files were created.", output)
        self.assertNotIn("Successfully generated all tag models", output)

    @mock.patch("sys.exit")
    def test_command_help(self, mock_exit):
        command = Command()
        command.stdout = StringIO()

        help_text = command.help

        self.assertIn("Generate tag models", help_text)
        self.assertTrue(callable(command.add_arguments))

        parser = mock.MagicMock()
        command.add_arguments(parser)
        self.assertTrue(parser.add_argument.called)

        # Check that both arguments are added
        call_args_list = parser.add_argument.call_args_list
        self.assertEqual(len(call_args_list), 2)

        # Check first argument is --dry-run
        first_args, _ = call_args_list[0]
        self.assertEqual(first_args[0], "--dry-run")

        # Check second argument is --force-reload
        second_args, _ = call_args_list[1]
        self.assertEqual(second_args[0], "--force-reload")

        mock_exit.assert_not_called()


class CustomTagGenerationTestCase(TagModelGenerationTestCase):
    @mock.patch("flexi_tag.management.commands.generate_tag_models.Template")
    @mock.patch(
        "flexi_tag.management.commands.generate_tag_models.apps.get_app_configs"
    )
    @mock.patch("flexi_tag.management.commands.generate_tag_models.import_module")
    @mock.patch("flexi_tag.management.commands.generate_tag_models.inspect.getfile")
    @mock.patch("flexi_tag.management.commands.generate_tag_models.open", create=True)
    def test_multiple_models_generation(
        self,
        mock_open,
        mock_getfile,
        mock_import_module,
        mock_get_app_configs,
        mock_template_class,
    ):
        mock_template = mock.MagicMock()
        mock_template.render.return_value = "# Mocked template content"
        mock_template_class.return_value = mock_template

        mock_model1 = self._create_mock_model(name="Model1", app_label="testapp")
        mock_model2 = self._create_mock_model(name="Model2", app_label="testapp")

        mock_app_config = mock.MagicMock()
        mock_app_config.get_models.return_value = [mock_model1, mock_model2]
        mock_get_app_configs.return_value = [mock_app_config]

        mock_module = mock.MagicMock()
        mock_import_module.return_value = mock_module
        test_file_path = os.path.join(self.test_dir, "models.py")
        mock_getfile.return_value = test_file_path

        mock_file = mock.MagicMock()
        mock_open.return_value.__enter__.return_value = mock_file

        out = StringIO()
        call_command("generate_tag_models", stdout=out)

        # With the fix, we should have only 1 file write (not 2) because both models
        # are written to the same file in a single operation
        self.assertEqual(mock_open.call_count, 1)
        self.assertEqual(mock_file.write.call_count, 1)
        self.assertIn("Successfully generated all tag models", out.getvalue())

    @mock.patch("flexi_tag.management.commands.generate_tag_models.Template")
    @mock.patch(
        "flexi_tag.management.commands.generate_tag_models.apps.get_app_configs"
    )
    @mock.patch("flexi_tag.management.commands.generate_tag_models.import_module")
    @mock.patch("flexi_tag.management.commands.generate_tag_models.inspect.getfile")
    @mock.patch("flexi_tag.management.commands.generate_tag_models.open", create=True)
    def test_model_with_special_name(
        self,
        mock_open,
        mock_getfile,
        mock_import_module,
        mock_get_app_configs,
        mock_template_class,
    ):
        mock_template = mock.MagicMock()
        mock_template.render.return_value = "# Mocked template content"
        mock_template_class.return_value = mock_template

        special_name = "Special_Model_Name-With.Characters$"
        mock_model = self._create_mock_model(name=special_name, app_label="testapp")

        mock_app_config = mock.MagicMock()
        mock_app_config.get_models.return_value = [mock_model]
        mock_get_app_configs.return_value = [mock_app_config]

        mock_module = mock.MagicMock()
        mock_import_module.return_value = mock_module
        test_file_path = os.path.join(self.test_dir, "models.py")
        mock_getfile.return_value = test_file_path

        mock_file = mock.MagicMock()
        mock_open.return_value.__enter__.return_value = mock_file

        out = StringIO()
        call_command("generate_tag_models", stdout=out)

        self.assertEqual(mock_template.render.call_count, 1)

        # With the new fix, the context contains a 'models' list instead of individual fields
        context = mock_template.render.call_args[0][0]
        models_data = context.get("models", [])
        self.assertEqual(len(models_data), 1, "Should have data for one model")

        model_data = models_data[0]
        self.assertEqual(model_data["model_name"], special_name)
        self.assertEqual(model_data["model_lower_name"], special_name.lower())
        self.assertIn("Successfully generated all tag models", out.getvalue())

    @mock.patch("flexi_tag.management.commands.generate_tag_models.Template")
    @mock.patch(
        "flexi_tag.management.commands.generate_tag_models.apps.get_app_configs"
    )
    @mock.patch("flexi_tag.management.commands.generate_tag_models.import_module")
    @mock.patch("flexi_tag.management.commands.generate_tag_models.inspect.getfile")
    @mock.patch("flexi_tag.management.commands.generate_tag_models.open", create=True)
    def test_command_with_non_django_class(
        self,
        mock_open,
        mock_getfile,
        mock_import_module,
        mock_get_app_configs,
        mock_template_class,
    ):
        mock_template = mock.MagicMock()
        mock_template.render.return_value = "# Mocked template content"
        mock_template_class.return_value = mock_template

        mock_model = mock.MagicMock()
        mock_model.__name__ = "NonModelClass"
        mock_model.__bases__ = (FlexiTagMixin,)
        mock_model._meta = mock.MagicMock()
        mock_model._meta.app_label = "testapp"
        mock_model.__module__ = "testapp.models"

        mock_app_config = mock.MagicMock()
        mock_app_config.get_models.return_value = [mock_model]
        mock_get_app_configs.return_value = [mock_app_config]

        mock_module = mock.MagicMock()
        mock_import_module.return_value = mock_module
        test_file_path = os.path.join(self.test_dir, "models.py")
        mock_getfile.return_value = test_file_path

        mock_file = mock.MagicMock()
        mock_open.return_value.__enter__.return_value = mock_file

        out = StringIO()
        call_command("generate_tag_models", stdout=out)

        self.assertIn("Successfully generated all tag models", out.getvalue())


class AutoImportTestCase(TagModelGenerationTestCase):
    def setUp(self):
        super(AutoImportTestCase, self).setUp()
        self.models_content = """
from django.db import models
from flexi_tag.utils.models import FlexiTagMixin

class TaggableManagerTestModel(FlexiTagMixin):
    name = models.CharField(max_length=100)
"""

    @mock.patch("flexi_tag.management.commands.generate_tag_models.Template")
    @mock.patch("flexi_tag.management.commands.generate_tag_models.inspect.getfile")
    @mock.patch("flexi_tag.management.commands.generate_tag_models.import_module")
    @mock.patch(
        "flexi_tag.management.commands.generate_tag_models.apps.get_app_configs"
    )
    def test_auto_import_single_model(
        self,
        mock_get_app_configs,
        mock_import_module,
        mock_getfile,
        mock_template_class,
    ):
        model_dir = os.path.join(self.test_dir, "testapp")
        if not os.path.exists(model_dir):
            os.makedirs(model_dir)
        models_path = os.path.join(model_dir, "models.py")

        with open(models_path, "w") as f:
            f.write(self.models_content)

        mock_template = mock.MagicMock()
        mock_template.render.return_value = "# Generated model content"
        mock_template_class.return_value = mock_template

        mock_model = self._create_mock_model(
            name="TaggableManagerTestModel", app_label="testapp"
        )

        mock_app_config = mock.MagicMock()
        mock_app_config.get_models.return_value = [mock_model]
        mock_get_app_configs.return_value = [mock_app_config]

        mock_import_module.return_value = mock.MagicMock()
        mock_getfile.return_value = os.path.join(model_dir, "__init__.py")

        out = StringIO()
        call_command("generate_tag_models", stdout=out)

        with open(models_path, "r") as f:
            updated_content = f.read()

        self.assertIn(
            "from .flexi_generated_model import TaggableManagerTestModelTag  # noqa",
            updated_content,
        )

    @mock.patch("flexi_tag.management.commands.generate_tag_models.Template")
    @mock.patch("flexi_tag.management.commands.generate_tag_models.inspect.getfile")
    @mock.patch("flexi_tag.management.commands.generate_tag_models.import_module")
    @mock.patch(
        "flexi_tag.management.commands.generate_tag_models.apps.get_app_configs"
    )
    def test_auto_import_multiple_models(
        self,
        mock_get_app_configs,
        mock_import_module,
        mock_getfile,
        mock_template_class,
    ):
        models_content = """
from django.db import models
from flexi_tag.utils.models import FlexiTagMixin

class FirstModel(FlexiTagMixin):
    name = models.CharField(max_length=100)

class SecondModel(FlexiTagMixin):
    title = models.CharField(max_length=200)
"""

        model_dir = os.path.join(self.test_dir, "testapp")
        if not os.path.exists(model_dir):
            os.makedirs(model_dir)
        models_path = os.path.join(model_dir, "models.py")

        with open(models_path, "w") as f:
            f.write(models_content)

        mock_template = mock.MagicMock()
        mock_template.render.return_value = "# Generated model content"
        mock_template_class.return_value = mock_template

        mock_model1 = self._create_mock_model(name="FirstModel", app_label="testapp")
        mock_model2 = self._create_mock_model(name="SecondModel", app_label="testapp")

        mock_app_config = mock.MagicMock()
        mock_app_config.get_models.return_value = [mock_model1, mock_model2]
        mock_get_app_configs.return_value = [mock_app_config]

        mock_import_module.return_value = mock.MagicMock()
        mock_getfile.return_value = os.path.join(model_dir, "__init__.py")

        out = StringIO()
        call_command("generate_tag_models", stdout=out)

        with open(models_path, "r") as f:
            updated_content = f.read()

        self.assertIn("from .flexi_generated_model import", updated_content)
        self.assertIn("FirstModelTag", updated_content)
        self.assertIn("SecondModelTag", updated_content)
        self.assertIn("# noqa", updated_content)


class MultipleModelsFixTestCase(TagModelGenerationTestCase):
    """Test cases for the fix that handles multiple FlexiTagMixin models correctly."""

    @mock.patch("flexi_tag.management.commands.generate_tag_models.Template")
    @mock.patch("flexi_tag.management.commands.generate_tag_models.inspect.getfile")
    @mock.patch("flexi_tag.management.commands.generate_tag_models.import_module")
    @mock.patch(
        "flexi_tag.management.commands.generate_tag_models.apps.get_app_configs"
    )
    def test_multiple_models_same_app_single_file_generation(
        self,
        mock_get_app_configs,
        mock_import_module,
        mock_getfile,
        mock_template_class,
    ):
        """Test that multiple models in the same app generate a single file with all tag models."""
        model_dir = os.path.join(self.test_dir, "testapp")
        if not os.path.exists(model_dir):
            os.makedirs(model_dir)

        # Mock template that supports multiple models
        mock_template = mock.MagicMock()
        expected_content = """
# 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


class FirstModelTag(models.Model):
    instance = models.OneToOneField(
        "testapp.FirstModel",
        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)


class SecondModelTag(models.Model):
    instance = models.OneToOneField(
        "testapp.SecondModel",
        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)

"""
        mock_template.render.return_value = expected_content
        mock_template_class.return_value = mock_template

        # Create mock models for the same app
        mock_model1 = self._create_mock_model(
            name="FirstModel", app_label="testapp", module="testapp.models"
        )
        mock_model2 = self._create_mock_model(
            name="SecondModel", app_label="testapp", module="testapp.models"
        )

        mock_app_config = mock.MagicMock()
        mock_app_config.get_models.return_value = [mock_model1, mock_model2]
        mock_get_app_configs.return_value = [mock_app_config]

        mock_import_module.return_value = mock.MagicMock()
        mock_getfile.return_value = os.path.join(model_dir, "__init__.py")

        out = StringIO()
        call_command("generate_tag_models", stdout=out)

        # Check that template.render was called with context containing both models
        render_call_args = mock_template.render.call_args[0][0]
        models_data = render_call_args.get("models", [])

        self.assertEqual(len(models_data), 2, "Should have data for both models")
        model_names = [model["model_name"] for model in models_data]
        self.assertIn("FirstModel", model_names)
        self.assertIn("SecondModel", model_names)

        # Check that the generated file path is correct
        expected_file_path = os.path.join(model_dir, "flexi_generated_model.py")
        self.assertTrue(os.path.exists(expected_file_path))

        # Check output messages
        output = out.getvalue()
        self.assertIn("Created tag model for testapp.FirstModel", output)
        self.assertIn("Created tag model for testapp.SecondModel", output)

    @mock.patch("flexi_tag.management.commands.generate_tag_models.Template")
    @mock.patch("flexi_tag.management.commands.generate_tag_models.inspect.getfile")
    @mock.patch("flexi_tag.management.commands.generate_tag_models.import_module")
    @mock.patch(
        "flexi_tag.management.commands.generate_tag_models.apps.get_app_configs"
    )
    def test_multiple_apps_with_multiple_models(
        self,
        mock_get_app_configs,
        mock_import_module,
        mock_getfile,
        mock_template_class,
    ):
        """Test that models from different apps generate separate files correctly."""
        # Create directories for two different apps
        app1_dir = os.path.join(self.test_dir, "app1")
        app2_dir = os.path.join(self.test_dir, "app2")
        # Create directories if they don't exist
        if not os.path.exists(app1_dir):
            os.makedirs(app1_dir)
        if not os.path.exists(app2_dir):
            os.makedirs(app2_dir)

        mock_template = mock.MagicMock()
        mock_template.render.return_value = "# Generated model content"
        mock_template_class.return_value = mock_template

        # Create models for app1
        mock_model1_app1 = self._create_mock_model(
            name="App1Model1", app_label="app1", module="app1.models"
        )
        mock_model2_app1 = self._create_mock_model(
            name="App1Model2", app_label="app1", module="app1.models"
        )

        # Create models for app2
        mock_model1_app2 = self._create_mock_model(
            name="App2Model1", app_label="app2", module="app2.models"
        )
        mock_model2_app2 = self._create_mock_model(
            name="App2Model2", app_label="app2", module="app2.models"
        )

        # Mock app configs
        mock_app_config1 = mock.MagicMock()
        mock_app_config1.get_models.return_value = [mock_model1_app1, mock_model2_app1]

        mock_app_config2 = mock.MagicMock()
        mock_app_config2.get_models.return_value = [mock_model1_app2, mock_model2_app2]

        mock_get_app_configs.return_value = [mock_app_config1, mock_app_config2]

        def mock_import_side_effect(module_name):
            return mock.MagicMock()

        def mock_getfile_side_effect(module):
            # Return different paths based on which app we're dealing with
            if hasattr(module, "_mock_name"):
                if "app1" in str(module._mock_name):
                    return os.path.join(app1_dir, "__init__.py")
                elif "app2" in str(module._mock_name):
                    return os.path.join(app2_dir, "__init__.py")
            return os.path.join(app1_dir, "__init__.py")  # Default

        mock_import_module.side_effect = mock_import_side_effect
        mock_getfile.side_effect = mock_getfile_side_effect

        out = StringIO()
        call_command("generate_tag_models", stdout=out)

        # Check that template.render was called twice (once for each app)
        self.assertEqual(mock_template.render.call_count, 2)

        # Check output contains models from both apps
        output = out.getvalue()
        self.assertIn("App1Model1", output)
        self.assertIn("App1Model2", output)
        self.assertIn("App2Model1", output)
        self.assertIn("App2Model2", output)

    @mock.patch("flexi_tag.management.commands.generate_tag_models.Template")
    @mock.patch("flexi_tag.management.commands.generate_tag_models.inspect.getfile")
    @mock.patch("flexi_tag.management.commands.generate_tag_models.import_module")
    @mock.patch(
        "flexi_tag.management.commands.generate_tag_models.apps.get_app_configs"
    )
    def test_dry_run_with_multiple_models(
        self,
        mock_get_app_configs,
        mock_import_module,
        mock_getfile,
        mock_template_class,
    ):
        """Test dry run mode with multiple models shows all models correctly."""
        mock_template = mock.MagicMock()
        mock_template.render.return_value = """
class Model1Tag(models.Model):
    pass

class Model2Tag(models.Model):
    pass
"""
        mock_template_class.return_value = mock_template

        mock_model1 = self._create_mock_model(
            name="Model1", app_label="testapp", module="testapp.models"
        )
        mock_model2 = self._create_mock_model(
            name="Model2", app_label="testapp", module="testapp.models"
        )

        mock_app_config = mock.MagicMock()
        mock_app_config.get_models.return_value = [mock_model1, mock_model2]
        mock_get_app_configs.return_value = [mock_app_config]

        mock_import_module.return_value = mock.MagicMock()
        mock_getfile.return_value = os.path.join(self.test_dir, "__init__.py")

        out = StringIO()
        call_command("generate_tag_models", "--dry-run", stdout=out)

        output = out.getvalue()
        self.assertIn("Would create file:", output)
        self.assertIn("Model1Tag", output)
        self.assertIn("Model2Tag", output)
        self.assertIn("Dry run completed. No files were created.", output)

    def test_update_imports_method_with_multiple_models(self):
        """Test the _update_imports helper method with multiple tag models."""
        from flexi_tag.management.commands.generate_tag_models import Command

        # Create a test models.py file
        models_content = """
from django.db import models
from flexi_tag.utils.models import FlexiTagMixin

class Model1(FlexiTagMixin):
    name = models.CharField(max_length=100)

class Model2(FlexiTagMixin):
    title = models.CharField(max_length=200)
"""
        models_file_path = os.path.join(self.test_dir, "models.py")
        with open(models_file_path, "w") as f:
            f.write(models_content)

        # Test the _update_imports method
        command = Command()
        tag_model_names = ["Model1Tag", "Model2Tag"]
        command._update_imports(models_file_path, tag_model_names)

        # Read the updated content
        with open(models_file_path, "r") as f:
            updated_content = f.read()

        # Check that both imports are added correctly
        self.assertIn(
            "from .flexi_generated_model import Model1Tag, Model2Tag  # noqa",
            updated_content,
        )

    def test_update_imports_method_with_existing_import(self):
        """Test the _update_imports helper method when an import already exists."""
        from flexi_tag.management.commands.generate_tag_models import Command

        # Create a test models.py file with existing import
        models_content = """
from django.db import models
from flexi_tag.utils.models import FlexiTagMixin
from .flexi_generated_model import OldModelTag  # noqa

class Model1(FlexiTagMixin):
    name = models.CharField(max_length=100)

class Model2(FlexiTagMixin):
    title = models.CharField(max_length=200)
"""
        models_file_path = os.path.join(self.test_dir, "models.py")
        with open(models_file_path, "w") as f:
            f.write(models_content)

        # Test the _update_imports method
        command = Command()
        tag_model_names = ["Model1Tag", "Model2Tag"]
        command._update_imports(models_file_path, tag_model_names)

        # Read the updated content
        with open(models_file_path, "r") as f:
            updated_content = f.read()

        # Check that the import is replaced with all new models
        self.assertIn(
            "from .flexi_generated_model import Model1Tag, Model2Tag  # noqa",
            updated_content,
        )
        self.assertNotIn("OldModelTag", updated_content)

    @mock.patch("flexi_tag.management.commands.generate_tag_models.Template")
    @mock.patch("flexi_tag.management.commands.generate_tag_models.inspect.getfile")
    @mock.patch("flexi_tag.management.commands.generate_tag_models.import_module")
    @mock.patch(
        "flexi_tag.management.commands.generate_tag_models.apps.get_app_configs"
    )
    def test_no_overwrite_issue_with_multiple_models(
        self,
        mock_get_app_configs,
        mock_import_module,
        mock_getfile,
        mock_template_class,
    ):
        """Test that multiple models don't overwrite each other in the generated file."""
        model_dir = os.path.join(self.test_dir, "testapp")
        # Create directory if it doesn't exist
        if not os.path.exists(model_dir):
            os.makedirs(model_dir)

        # This test verifies the core fix: that we write once with all models
        # instead of writing multiple times and overwriting

        written_content = []

        def mock_file_write(content):
            written_content.append(content)

        mock_template = mock.MagicMock()
        mock_template.render.return_value = "# Content with both models"
        mock_template_class.return_value = mock_template

        mock_model1 = self._create_mock_model(
            name="FirstModel", app_label="testapp", module="testapp.models"
        )
        mock_model2 = self._create_mock_model(
            name="SecondModel", app_label="testapp", module="testapp.models"
        )

        mock_app_config = mock.MagicMock()
        mock_app_config.get_models.return_value = [mock_model1, mock_model2]
        mock_get_app_configs.return_value = [mock_app_config]

        mock_import_module.return_value = mock.MagicMock()
        mock_getfile.return_value = os.path.join(model_dir, "__init__.py")

        # Mock the file writing to track how many times it's called
        with mock.patch("builtins.open", mock.mock_open()) as mock_file:
            mock_file.return_value.write.side_effect = mock_file_write

            out = StringIO()
            call_command("generate_tag_models", stdout=out)

            # The key assertion: file should be opened for writing only ONCE per app
            # This is the fix - before, it would open and write multiple times
            write_calls = [
                call for call in mock_file.call_args_list if "w" in str(call)
            ]
            self.assertEqual(
                len(write_calls),
                1,
                "File should be opened for writing only once per app, not once per model",
            )

            # Template should be rendered only once with context for all models
            self.assertEqual(
                mock_template.render.call_count,
                1,
                "Template should be rendered once with all models",
            )

            # Check that the context contains both models
            render_context = mock_template.render.call_args[0][0]
            models_data = render_context.get("models", [])
            self.assertEqual(len(models_data), 2, "Context should contain both models")
