# -*- coding: utf-8 -*-
"""_____________________________________________________________________

:PROJECT: LARAsuite

*generate unittests *

:details: generate_grpc_client - a unittests generator for LARA-django.
          This is a LARA-django-base management command to generate a unittests for a
          given app. The generated client is a python module that can be used to
          communicate with the gRPC server of the given app.

          requirements:
                python-lorem
:usage: lara-django-dev generate_unittests --author-name "mark doerr" --author-email "mark.doerr@uni-greifswald.de" --force lara_django_base

:authors: mark doerr <mark.doerr@uni-greifswald.de>, Kyle Moynahan <moynahan@campus.tu-berlin.de>

.. note:: -
.. todo:: - https://stackoverflow.com/questions/11380413/python-unittest-passing-arguments
          - speed improvemnts https://medium.com/@thehackadda/tips-for-speeding-up-your-django-tests-35bab8250760
________________________________________________________________________
"""

import re
import os
import logging
import lorem  # pip install python-lorem
import random
import uuid
import json
import pdb
from datetime import datetime

from django.apps import apps
from django.conf import settings
from django.core.management.base import LabelCommand, CommandError
from django.db import models

lara_django_apps = tuple(filter(lambda x: "lara_django" in x, settings.__dict__["INSTALLED_APPS"]))


# Configurable constants
MAX_LINE_WIDTH = getattr(settings, "MAX_LINE_WIDTH", 120)
INDENT_WIDTH = getattr(settings, "INDENT_WIDTH", 4)

UNITTEST_FILE_HEADER = """\"\"\"_____________________________________________________________________

:PROJECT: LARAsuite

*{lara_app} - {model.__name__} gRPC API unittests *

:details: {lara_app} - {model.__name__} gRPC API unittests.
         - generated with 'lara-django-dev generate_unittests_grpc --author-name {lara_author} --author-email {lara_author_email} {lara_app}'
         to run the tests with pytest use:
            pytest --durations=0 -s --num-instances 1 --host localhost --port 50051 [path-to-tests]
:authors: {lara_author} <{lara_author_email}>

.. note:: -
.. todo:: -
________________________________________________________________________
\"\"\"\n"""


def indent(level=1, text="", end="\n"):
    return "\n".join([(level * INDENT_WIDTH * " ") + line for line in text.split("\n")]) + end


def lower_strip(s):
    return s.lower().replace(" ", "").strip()


class Command(LabelCommand):
    help = """Generate a unittests for a given LARA app (models)"""
    args = "[app_name, author_name, author_email, num_instances]"
    can_import_settings = True

    def add_arguments(self, parser):
        parser.add_argument("app_name", help="Name of the LARA app to generate the unittests for")

        parser.add_argument("--author-name", help="Name of the author", default="lara")
        parser.add_argument("--author-email", help="Email of the author", default="lara@lara.org")
        parser.add_argument("--num-instances", help="Number of test cases to generate", default="1")
        parser.add_argument("--force", action="store_true", help="overwrite existing files", default=False)
        # parser.add_argument('model_name', nargs='*')

    # @signalcommand
    def handle(self, *args, **options):
        self.app_name = options["app_name"]

        logging.warning("!! only a scaffold is generated, please check/add content to the generated files !!!")

        try:
            app = apps.get_app_config(self.app_name)
        except LookupError:
            self.stderr.write("This command requires an existing app name as argument")
            self.stderr.write("Available apps:")
            app_labels = [app.label for app in apps.get_app_configs()]
            for label in sorted(app_labels):
                self.stderr.write("    %s" % label)
            return

        model_res = []
        # for arg in options['model_name']:
        #     model_res.append(re.compile(arg, re.IGNORECASE))

        AppUnittestGenerator(app, model_res, options)

        # self.stdout.write()


class AppUnittestGenerator:
    def __init__(self, app_config, model_res, options):
        self.app_config = app_config
        self.model_res = model_res

        self.app_name = options["app_name"]
        self.author_name = options["author_name"]
        self.author_email = options["author_email"]
        self.num_instances = options["num_instances"]
        self.force_overwrite = options["force"]

        self.model_dict = {}
        self.internal_model_name = set()
        self.external_model_name = set()
        self.external_model_names_all = set()
        
        self.model_names = [model.__name__ for model in self.app_config.get_models()]
        self.foreignkey_fields = {}
        self.required_grpc_imports = ["lara_django_base", self.app_name]
        self.required_list_functions = {}

        for model in self.app_config.get_models():
            self.unittests_str = ""
            self.unittests_header_str = ""
            self.unittests_imports_str = ""
            self.unittests_fixture_str = ""
            self.generate_unittests(model)

        self.generate_unittest_config(model)

    def generate_unittests(self, model):
        self.generate_unittests_header(model)
        self.generate_model_tests(model)
        self.generate_unittests_fixtures(model)
        self.generate_unittests_imports(model)

        self.unittests_str = self.unittests_header_str + self.unittests_imports_str + self.unittests_fixture_str + self.unittests_str

        # save unittests to file
        unittest_filename = f"test_{self.app_name}_{model.__name__}_grpc.py"

        print(f"writing {unittest_filename} to {os.getcwd()}")
        #print(self.unittests_str)

        if os.path.isfile(unittest_filename):
            if self.force_overwrite:
                with open(unittest_filename, "w") as f:
                    f.write(self.unittests_str)
            else:
                append = input(
                    f"{unittest_filename} already exists,\n press 'a' to append,\n press 'o' to overwrite,\n or 'n' to do nothing: "
                )
                if append.lower() == "a":
                    with open(unittest_filename, "a") as f:
                        f.write(self.unittests_str)
                elif append.lower() == "o":
                    with open(unittest_filename, "w") as f:
                        f.write(self.unittests_str)
        else:
            with open(unittest_filename, "w") as f:
                f.write(self.unittests_str)

    def generate_unittests_header(self, model):
        self.unittests_header_str = UNITTEST_FILE_HEADER.format(
            lara_app=self.app_name,
            lara_author=self.author_name,
            lara_author_email=self.author_email,
            lara_num_instances=self.num_instances,
            model=model,
        )

    def generate_unittests_imports(self, model):
        # pdb.set_trace()
        # models = [model for model in self.app_config.get_models()]
        # fields = [field for field in models[3]._meta.get_fields()]
        grpc_imports = ""
        async_functions = ""
        for app in self.required_grpc_imports:
            grpc_imports += f"import {app}_grpc.v1.{app}_pb2 as {app}_pb2\n"
            grpc_imports += f"import {app}_grpc.v1.{app}_pb2_grpc as {app}_pb2_grpc\n\n"
       
        self.unittests_imports_str += (
            f"""
import json
import logging
import uuid
import asyncio
import grpc
from google.protobuf.struct_pb2 import Struct
import pytest
import pytest_asyncio
import random

{grpc_imports}

#import lara_django_APP_grpc.v1.lara_django_APP_pb2 as lara_django_APP_pb2
#import lara_django_APP_grpc.v1.lara_django_APP_pb2_grpc as lara_django_APP_pb2_grpc

\n""" )
        
    def generate_unittests_fixtures(self, model):
        
        self.unittests_fixture_str += f"""@pytest_asyncio.fixture(scope="module")
async def {model.__name__.lower()}_client(grpc_channel):
    return {self.app_name}_pb2_grpc.{model.__name__}ControllerStub(grpc_channel)
\n"""
        
        self.unittests_fixture_str += "# " + ", ".join([f"{external_model.lower()}_ids" for external_model in self.external_model_name]) + "\n\n"
        
    def generate_model_tests(self, model):
        self.unittests_str += f"""
@pytest_asyncio.fixture(scope="module")
async def {model.__name__.lower()}_minimal({model.__name__.lower()}_client, num_instances, namespace_ids):
    # !! add all fixtures in parameter list above !!

    namespace_id_list = namespace_ids

    {model.__name__.lower()}_list = []

    for i in range(num_instances):
        print("Creating  {model.__name__}")

        test_{model.__name__.lower()} = dict(
            # !! add fields here !!
        )
        create_res = await {model.__name__.lower()}_client.Create({self.app_name.lower()}_pb2.{model.__name__}Request(** test_{model.__name__.lower()}))
        {model.__name__.lower()}_list.append(create_res.{model.__name__.lower()}_id)
    
    print({model.__name__.lower()}_list)
        
    yield {model.__name__.lower()}_list

@pytest_asyncio.fixture(scope="module")
async def {model.__name__.lower()}({model.__name__.lower()}_client, num_instances, namespace_ids):

    namespace_id_list = namespace_ids

    {model.__name__.lower()}_list = []

    for i in range(num_instances):
        print("Creating  {model.__name__}")

        test_{model.__name__.lower()} = dict(
{ self.parse_fields_for_init(model) }
        )
        create_res = await {model.__name__.lower()}_client.Create({self.app_name.lower()}_pb2.{model.__name__}Request(** test_{model.__name__.lower()}))
        {model.__name__.lower()}_list.append(create_res.{model.__name__.lower()}_id)

    print({model.__name__.lower()}_list)
        
    yield {model.__name__.lower()}_list

@pytest.mark.asyncio(loop_scope="session")
class Test{model.__name__}MinimalCRUD:
    loop: asyncio.AbstractEventLoop

    @pytest.mark.skip(reason="Not fully implemented yes")
    async def test_{model.__name__.lower()}_create(self, {model.__name__.lower()}_client, {model.__name__.lower()}_minimal, num_instances, namespace_ids):
        print("Creating: {model.__name__.lower()}_minimal")
        print({model.__name__.lower()}_minimal)
        assert len({model.__name__.lower()}_minimal) == num_instances

    @pytest.mark.skip(reason="Not fully implemented yes")
    async def test_{model.__name__.lower()}_retrieve(self, {model.__name__.lower()}_client, {model.__name__.lower()}_minimal):
        print("Retrieving {model.__name__.lower()}_minimal")
        print({model.__name__.lower()}_minimal)
        response = await {model.__name__.lower()}_client.Retrieve({self.app_name.lower()}_pb2.{model.__name__}RetrieveRequest({model.__name__.lower()}_id={model.__name__.lower()}_minimal[0]))
        assert response.{model.__name__.lower()}_id == {model.__name__.lower()}_minimal[0]

    @pytest.mark.skip(reason="Not fully implemented yet")
    async def test_{model.__name__.lower()}_update(self, {model.__name__.lower()}_client, {model.__name__.lower()}_minimal):
        print("Updating {model.__name__.lower()}_minimal")
        print({model.__name__.lower()}_minimal)
        {model.__name__.lower()}_updated = dict({model.__name__.lower()}_id={model.__name__.lower()}_minimal[0], name="test_{model.__name__.lower()}_name_updated", description="{model.__name__.lower()} description updated")

        response_update = await {model.__name__.lower()}_client.Update({self.app_name.lower()}_pb2.{model.__name__}Request(**{model.__name__.lower()}_updated))
        
        response_retr = await {model.__name__.lower()}_client.Retrieve({self.app_name.lower()}_pb2.{model.__name__}RetrieveRequest({model.__name__.lower()}_id={model.__name__.lower()}_minimal[0]))

        assert response_retr.name == "test_{model.__name__.lower()}_name_updated"

    @pytest.mark.skip(reason="Not fully implemented yet")
    async def test_{model.__name__.lower()}_destroy(self, {model.__name__.lower()}_client, {model.__name__.lower()}_minimal):
        print("Deleting all items in {model.__name__.lower()}_minimal")
        print({model.__name__.lower()}_minimal)

        for {model.__name__.lower()}_id in {model.__name__.lower()}_minimal:
            response = await {model.__name__.lower()}_client.Destroy({self.app_name.lower()}_pb2.{model.__name__}DestroyRequest({model.__name__.lower()}_id={model.__name__.lower()}_id))
            print(f"destroying item: {{{model.__name__.lower()}_id}}")
            # assert response == [{model.__name__.lower()}_id]

@pytest.mark.asyncio(loop_scope="session")
class Test{model.__name__}CRUD:
    loop: asyncio.AbstractEventLoop

    @pytest.mark.skip(reason="Not fully implemented yes")
    async def test_{model.__name__.lower()}_create(self, {model.__name__.lower()}_client, {model.__name__.lower()}, num_instances, namespace_ids):
        print("Creating: {model.__name__.lower()}")
        print({model.__name__.lower()})

        assert len({model.__name__.lower()}) == num_instances

    @pytest.mark.skip(reason="Not fully implemented yes")
    async def test_{model.__name__.lower()}_list(self, {model.__name__.lower()}_client, {model.__name__.lower()}):
        print("List {model.__name__.lower()}")
        print({model.__name__.lower()})

        filter_as_dict = {{"name__contains": "test_{model.__name__.lower()}_name"}}
        metadata = (("filters", (json.dumps(filter_as_dict))),)
        response = await {model.__name__.lower()}_client.List({self.app_name.lower()}_pb2.{model.__name__}ListRequest(), metadata=metadata)
        assert len(response.results) == len({model.__name__.lower()}) 

    @pytest.mark.skip(reason="Not fully implemented yes")
    async def test_{model.__name__.lower()}_retrieve(self, {model.__name__.lower()}_client, {model.__name__.lower()}):
        print("Retrieving {model.__name__.lower()}")
        print({model.__name__.lower()})

        for {model.__name__.lower()}_id in {model.__name__.lower()}:
            response = await {model.__name__.lower()}_client.Retrieve({self.app_name.lower()}_pb2.{model.__name__}RetrieveRequest({model.__name__.lower()}_id={model.__name__.lower()}_id))
            assert response.{model.__name__.lower()}_id == {model.__name__.lower()}_id

    @pytest.mark.skip(reason="Not fully implemented yet")
    async def test_{model.__name__.lower()}_update(self, {model.__name__.lower()}_client, {model.__name__.lower()}):
        print("Updating {model.__name__.lower()}")
        print({model.__name__.lower()})

        {model.__name__.lower()}_updated = dict({model.__name__.lower()}_id={model.__name__.lower()}[0], name="test_{model.__name__.lower()}_name_updated", description="{model.__name__.lower()} description updated")

        response_update = await {model.__name__.lower()}_client.Update({self.app_name.lower()}_pb2.{model.__name__}Request(**{model.__name__.lower()}_updated))
        
        response_retr = await {model.__name__.lower()}_client.Retrieve({self.app_name.lower()}_pb2.{model.__name__}RetrieveRequest({model.__name__.lower()}_id={model.__name__.lower()}[0]))

        assert response_retr.name == "test_{model.__name__.lower()}_name_updated"

    @pytest.mark.skip(reason="Not fully implemented yet")
    async def test_{model.__name__.lower()}_destroy(self, {model.__name__.lower()}_client, {model.__name__.lower()}):
        print("Destroy all items in {model.__name__.lower()}")
        print({model.__name__.lower()})
        
        for {model.__name__.lower()}_id in {model.__name__.lower()}:
            response = await {model.__name__.lower()}_client.Destroy({self.app_name.lower()}_pb2.{model.__name__}DestroyRequest({model.__name__.lower()}_id={model.__name__.lower()}_id))
            print(f"destroying item: {{{model.__name__.lower()}_id}}")
            # assert response == [{model.__name__.lower()}_id]
    

"""

    def parse_fields_for_init(self, model):
        # TODO: add automatic handling here
        fields_str = ""

        fk_fields = {}

        for field in model._meta.get_fields():
            #print(field)
            unique_id = ""
            # print(f"field: {field} --> {type(field)} \n")

            # if model.__name__.lower() == "sample":
            #     pdb.set_trace()
            if (
                isinstance(field, models.fields.related.ManyToManyField)
                or isinstance(field, models.fields.reverse_related.ManyToOneRel)
                or isinstance(field, models.fields.reverse_related.ManyToManyRel)
                or isinstance(field, models.fields.reverse_related.OneToOneRel)
                or isinstance(field, models.fields.reverse_related.ManyToManyRel)
            ):
                required = False
                
            else:
                required = field.blank == False
            if required:
                unique_id = "_{i}"

            match field:
                case models.fields.related.ManyToOneRel():
                    pass
                case models.fields.UUIDField():
                    # TODO: need to handle these
                    # fields_str += indent(
                    #     3,
                    #     f"{field.model.__name__.lower()}_id = {field.model.__name__.lower().replace('_','')}_ids[random.randrange(len({field.model.__name__.lower().replace('_','')}_ids))],  # required:{required} (ManyToManyField -> {field.model.__name__}) {field.help_text}3",
                    # )

                    fk_fields[field.model.__name__.lower()] = {
                        "related_model": field.model.__name__.replace("_", ""),
                        "app": None,
                    }
                    # pdb.set_trace()
                    # self.external_model_name.add("UUID")
                    # pdb.set_trace()

                case models.fields.BooleanField():
                    fields_str += indent(
                        3,
                        f"{field.name} = {random.choice(('True', 'False') ) },   # required:{required} (bool) {field.help_text}",
                    )
                case models.fields.URLField():
                    url_str = f"http://www.{lorem.get_word()}.org/lara"
                    if field.name == "url":
                        url_str = f"http://www.{lorem.get_word()}.org/lara"
                    elif field.name == "iri":
                        url_str = f"http://w3id.org/lara/{model.__name__}_{lorem.get_word()}_{{i}}"

                    fields_str += indent(
                        3,
                        f'{field.name} = f"{url_str}",   # required:{required} (URL - str) {field.help_text}',
                    )
                case models.fields.CharField():
                    
                    fields_str += indent(
                        3,
                        f'{field.name} = f"{lorem.get_word()}{unique_id}",   # required:{required} (str) {field.help_text}',
                    )

                case models.fields.TextField():
                    if field.name == "name":
                        fields_str += indent(
                            3,
                            f'{field.name} = f"test_{model.__name__.lower()}_name_{{i}}",   #required:{required} (str) {field.help_text}',
                        )
                    elif field.name == "name_full":
                        fields_str += indent(
                            3,
                            f'{field.name} = f"test_{model.__name__.lower()}_name_full_{{i}}",   #required:{required} (str) {field.help_text}',
                        )
                    elif field.name == "title":
                        fields_str += indent(
                            3,
                            f'{field.name} = f"test_{model.__name__.lower()}_title_{{i}}",   #required:{required} (str) {field.help_text}',
                        )
                    else:
                        fields_str += indent(
                            3,
                            f'{field.name} = f"{lorem.get_sentence()}{unique_id}",   # required:{required}(str) {field.help_text}',
                        )

                case models.fields.IntegerField():
                    fields_str += indent(
                        3,
                        f"{field.name} = {random.randint(1,100)},   #required:{required} (int) {field.help_text}",
                    )

                case models.fields.FloatField():
                    fields_str += indent(
                        3,
                        f"{field.name} = {random.uniform(1.0, 100.0)},   #required:{required} (float) {field.help_text}",
                    )

                case models.fields.DecimalField():
                    fields_str += indent(
                        3,
                        f"{field.name} = {random.uniform(1.0, 100.0)},   #required:{required} (float) {field.help_text}",
                    )

                case models.fields.DateTimeField():
                    fields_str += indent(
                        3,
                        f'{field.name} = "{datetime.now()}",   #required:{required} (datetime) {field.help_text}',
                    )

                case models.fields.DateField():
                    fields_str += indent(
                        3,
                        f'{field.name} = "{datetime.now().date()}",   #required:{required} (date) {field.help_text}',
                    )

                case models.fields.json.JSONField():
                    # random json
                    rand_json = json.dumps(
                        {"fields": {"key1": lorem.get_word(), "key2": lorem.get_word(), "key3": lorem.get_word()}}
                    )
                    fields_str += indent(
                        3,
                        f"{field.name} = Struct().update({rand_json}),   #required:{required} (str) {field.help_text}",
                    )

                case models.fields.files.FileField():
                    fields_str += indent(2, f"### FILES NOT HANDLED YET")
                    fields_str += indent(
                        3, f'{field.name} = "my_file.md",  #required:{required} (file) {field.help_text}'
                    )

                case models.fields.related.ForeignKey():
                    # print(f"{field.name} --- attributes: {field.__dict__}")
                    fields_str += indent(
                        3,
                        f"{field.name} = random.choice({field.related_model.__name__.lower()}_ids) if len({field.related_model.__name__.lower()}_ids) > 0 else None,  # required:{required} (ForeignKey ->  {field.related_model.__name__.lower()}) {field.help_text}",
                    )
                    fk_fields[field.name] = {"related_model": field.related_model.__name__, "app": None}
                    
                    self.external_model_name.add(field.related_model.__name__)

                    self.model_dict[field.related_model.__name__] = field.related_model

                case models.OneToOneField():
                    fields_str += indent(
                        3,
                        f"{field.name} =  ,  # required:{required} (OneToOneField -> {field.related_model.__name__}) {field.help_text}",
                    )
                    self.external_model_name.add(field.related_model.__name__)

                    self.model_dict[field.related_model.__name__] = field.related_model

                case models.fields.related.ManyToManyField():
                    # print(f"field attributes: {field.__dict__}")
                    fields_str += indent(
                        3,
                        f"{field.name} = [random.choice({field.related_model.__name__.lower()}_ids)] if len({field.related_model.__name__.lower()}_ids) > 0 else None,  # required:{required} (ManyToManyField -> {field.related_model.__name__}) {field.help_text}",
                    )
                    self.external_model_name.add(field.related_model.__name__)

                    fk_fields[field.name] = {"related_model": field.related_model.__name__, "app": None}
                    self.model_dict[field.related_model.__name__] = field.related_model

        self.external_model_names_all.update(self.external_model_name)
        return fields_str

    def get_relational_fields(self, model):
        foreignkey_fields = set()
        related_names = set()
        m2m_fields = set()
        one2one_fields = set()

        for field in model._meta.get_fields():
            print(f"field: {field} --> {type(field)} ")
            if isinstance(field, models.ForeignKey):
                foreignkey_fields.add(field.name)
            if isinstance(field, models.fields.related.ForeignKey):
                pass
                # print(f"field attributes: {field.__dict__}")
                # related_names.add(field.name)
            if isinstance(field, models.fields.related.ManyToManyField):
                # print(f"field attributes: {field.__dict__}")
                m2m_fields.add(field.name)
            if isinstance(field, models.fields.reverse_related.ManyToManyRel):
                # print(f"field attributes: {field.__dict__}")
                related_names.add(field.name)
                related_names.add(field.related_name)
                related_names.add(field.related_query_name)
                related_names.add(field.field.name)
                # related_names.add(field.field.related_name)
                related_names.add(field.field.related_query_name)
            if isinstance(field, models.fields.related.OneToOneField):
                one2one_fields.add(field.name)
            if isinstance(field, models.fields.reverse_related.OneToOneRel):
                related_names.add(field.name)
                related_names.add(field.related_name)
                related_names.add(field.related_query_name)
        return [foreignkey_fields, m2m_fields, one2one_fields, related_names]

    def generate_unittest_config(self, model):
        self.unittests_config_str = ""
        self.unittests_header_str = ""
        model.__name__= "PyTest Config"

        self.generate_unittests_header(model)
        self.generate_unittests_config_body()
        self.generate_external_fixtures()

        self.unittests_conf_str = self.unittests_header_str + self.unittests_config_str

        self.write_pytest_config()

    def generate_unittests_config_body(self):
        grpc_imports = ""
        for app in self.required_grpc_imports:
            grpc_imports += f"import {app}_grpc.v1.{app}_pb2 as {app}_pb2\n"
            grpc_imports += f"import {app}_grpc.v1.{app}_pb2_grpc as {app}_pb2_grpc\n\n"

        self.unittests_config_str += f"""        
import grpc
import asyncio
import pytest
import pytest_asyncio

{grpc_imports}

def pytest_addoption(parser):
    parser.addoption("--host", action="store", default="localhost")
    parser.addoption("--port", action="store", default=50066)
    parser.addoption("--num-instances", action="store", default=1, help="Number of instances to create test")


@pytest.fixture(scope="session")
def num_instances(pytestconfig):
    return int(pytestconfig.getoption("num_instances"))


@pytest_asyncio.fixture(scope="session")
async def grpc_channel(pytestconfig):
    print(f"Creating gRPC channel: {{pytestconfig.getoption('host')}}:{{pytestconfig.getoption('port')}}" )
    channel = grpc.aio.insecure_channel(f"{{pytestconfig.getoption('host')}}:{{pytestconfig.getoption('port')}}")

    yield channel

    print(f"\\nAll tests executed !\\nClosing gRPC channel: {{pytestconfig.getoption('host')}}:{{pytestconfig.getoption('port')}}" )
    await channel.close()

"""

    def generate_external_fixtures(self):
        for external_model in self.external_model_names_all:
            self.unittests_config_str  += f"""@pytest_asyncio.fixture(scope="module")
async def {external_model.lower()}_ids(pytestconfig, grpc_channel):
    created_item_ids = []
    {external_model.lower()}_client = lara_django_APP_pb2_grpc.{external_model}ControllerStub(grpc_channel)
    res = await {external_model.lower()}_client.List(lara_django_APP_pb2.{external_model}ListRequest())

    {external_model.lower().replace('_','')}_ids = [r.{external_model.lower().replace('_','')}_id for r in res.results]

    if len(res.results) == 0:
        for i in range(3):
            print(f"Creating  {external_model.lower().replace('_','')}")
            test_{external_model.lower().replace('_','')} = dict(
                # !! add fields here !!
            )
            create_res = await {external_model.lower()}_client.Create(lara_django_APP_pb2.{external_model}Request(** test_{external_model.lower().replace('_','')}))
            created_item_ids.append(create_res.{external_model.lower().replace('_','')}_id)
        {external_model.lower().replace('_','')}_ids = created_item_ids

    yield {external_model.lower().replace('_','')}_ids

    if len(created_item_ids) > 0:
        for {external_model.lower().replace('_','')}_id in created_item_ids:
            response = await {external_model.lower()}_client.Destroy(lara_django_APP_pb2.{external_model}DestroyRequest({external_model.lower().replace('_','')}_id={external_model.lower().replace('_','')}_id))
            print(f"\\n+++ finally destroying external {external_model.lower().replace('_','')}_id: {{{external_model.lower().replace('_','')}_id}}")
\n"""

    def write_pytest_config(self):
        # save unittests to file
        unittest_config_filename = "conftest.py"

        print(f"writing {unittest_config_filename} to {os.getcwd()}")
        #print(self.unittests_str)

        if os.path.isfile(unittest_config_filename):
            if self.force_overwrite:
                with open(unittest_config_filename, "w") as f:
                    f.write(self.unittests_conf_str)
            else:
                append = input(
                    f"{unittest_config_filename} already exists,\n press 'a' to append,\n press 'o' to overwrite,\n or 'n' to do nothing: "
                )
                if append.lower() == "a":
                    with open(unittest_config_filename, "a") as f:
                        f.write(self.unittests_conf_str)
                elif append.lower() == "o":
                    with open(unittest_config_filename, "w") as f:
                        f.write(self.unittests_conf_str)
        else:
            with open(unittest_config_filename, "w") as f:
                f.write(self.unittests_conf_str)
        

