"""
Tests complets pour le système de génération de types GraphQL.

Ce module teste:
- La génération de types GraphQL à partir de modèles Django
- La conversion des champs Django vers GraphQL
- La gestion des relations entre types
- La validation des types générés
- L'optimisation des performances de génération
"""

from typing import Dict, List, Optional, Type
from unittest.mock import MagicMock, Mock, patch

import graphene
import pytest
from django.contrib.auth.models import User
from django.db import models
from django.test import TestCase
from graphene import Boolean, DateTime, Int
from graphene import List as GrapheneList
from graphene import ObjectType, String
from graphene_django import DjangoObjectType
from test_app.models import Category, Post, Tag

from rail_django_graphql.generators.introspector import FieldInfo, ModelIntrospector
from rail_django_graphql.generators.types import TypeGenerator


class TestTypeGenerator(TestCase):
    """Tests pour la classe TypeGenerator."""

    def setUp(self):
        """Configuration des tests."""
        self.introspector = ModelIntrospector(Category)
        self.type_generator = TypeGenerator()

    def test_initialization(self):
        """Test l'initialisation du générateur de types."""
        # Test initialisation avec settings
        generator = TypeGenerator()
        self.assertIsNotNone(generator)
        self.assertIsNotNone(generator.settings)
        self.assertIsNotNone(generator.mutation_settings)

        # Test initialisation avec configuration
        from rail_django_graphql.core.settings import TypeGeneratorSettings

        settings_mock = TypeGeneratorSettings()
        generator_with_config = TypeGenerator(settings=settings_mock)
        self.assertIsNotNone(generator_with_config)
        self.assertEqual(generator_with_config.settings, settings_mock)

    def test_generate_type_basic(self):
        """Test la génération de type GraphQL de base."""
        # Générer le type pour Category
        category_type = self.type_generator.generate_object_type(Category)

        # Vérifier que le type est généré
        self.assertIsNotNone(category_type)
        self.assertTrue(issubclass(category_type, DjangoObjectType))

        # Vérifier le nom du type
        self.assertEqual(category_type._meta.name, "CategoryType")

        # Vérifier le modèle associé
        self.assertEqual(category_type._meta.model, Category)

    def test_generate_type_fields_mapping(self):
        """Test le mapping des champs Django vers GraphQL."""
        category_type = self.type_generator.generate_object_type(Category)

        # Obtenir les champs du type généré
        fields = category_type._meta.fields

        # Vérifier la présence des champs attendus
        expected_fields = [
            "name",
            "description",
        ]

        for field_name in expected_fields:
            self.assertIn(field_name, fields)

    def test_field_type_conversion(self):
        """Test la conversion des types de champs Django vers GraphQL."""
        # Skip this test as get_graphql_type_for_field function doesn't exist
        self.skipTest(
            "get_graphql_type_for_field function not found in current implementation"
        )

    def test_generate_type_with_relationships(self):
        """Test la génération de types avec des relations."""
        # Générer le type pour Post (qui a des relations)
        post_type = self.type_generator.generate_object_type(Post)

        # Vérifier que le type est généré
        self.assertIsNotNone(post_type)

        # Vérifier les champs de relation
        fields = post_type._meta.fields
        self.assertIn("category", fields)
        self.assertIn("tags", fields)

    def test_generate_type_caching(self):
        """Test le système de cache pour la génération de types."""
        # Premier appel - doit créer le type
        category_type_1 = self.type_generator.generate_object_type(Category)

        # Deuxième appel - doit utiliser le cache
        category_type_2 = self.type_generator.generate_object_type(Category)

        # Vérifier que c'est le même type (cache)
        self.assertIs(category_type_1, category_type_2)

    def test_generate_multiple_types(self):
        """Test la génération de plusieurs types."""
        models_to_test = [Category, Post, Tag]
        generated_types = {}

        for model in models_to_test:
            generated_types[model] = self.type_generator.generate_object_type(model)

        # Vérifier que tous les types sont générés
        self.assertEqual(len(generated_types), 3)

        # Vérifier que chaque type est unique
        for model, generated_type in generated_types.items():
            self.assertIsNotNone(generated_type)
            self.assertEqual(generated_type._meta.model, model)

    def test_nullable_fields_handling(self):
        """Test la gestion des champs nullable."""
        category_type = self.type_generator.generate_object_type(Category)

        # Vérifier que les champs nullable sont correctement gérés
        # description est nullable (null=True, blank=True)
        fields = category_type._meta.fields
        self.assertIn("description", fields)

    def test_field_validation(self):
        """Test la validation des champs générés."""
        category_type = self.type_generator.generate_object_type(Category)

        # Vérifier que tous les champs ont des types valides
        fields = category_type._meta.fields

        for field_name, field in fields.items():
            # Chaque champ doit avoir un type GraphQL valide
            self.assertIsNotNone(field)

            # Vérifier que le champ a les attributs nécessaires
            if hasattr(field, "type"):
                self.assertIsNotNone(field.type)

    def test_meta_information_preservation(self):
        """Test la préservation des informations meta."""
        category_type = self.type_generator.generate_object_type(Category)

        # Vérifier que les informations meta sont préservées
        self.assertEqual(category_type._meta.model, Category)
        self.assertIsNotNone(category_type._meta.name)

    def test_error_handling_invalid_model(self):
        """Test la gestion d'erreurs pour un modèle invalide."""
        with self.assertRaises((AttributeError, TypeError)):
            self.type_generator.generate_object_type(None)

    def test_performance_large_model(self):
        """Test les performances avec un modèle complexe."""
        import time

        # Mesurer le temps de génération
        start_time = time.time()
        post_type = self.type_generator.generate_object_type(Post)
        end_time = time.time()

        # La génération doit être rapide (moins de 100ms)
        execution_time = end_time - start_time
        self.assertLess(execution_time, 0.1)

        # Vérifier que le type est correctement généré
        self.assertIsNotNone(post_type)
        self.assertTrue(len(post_type._meta.fields) > 0)

    def test_type_name_generation(self):
        """Test la génération des noms de types."""
        # Test avec différents modèles
        test_cases = [
            (Category, "CategoryType"),
            (Post, "PostType"),
            (Tag, "TagType"),
        ]

        for model, expected_name in test_cases:
            with self.subTest(model=model):
                generated_type = self.type_generator.generate_object_type(model)
                self.assertEqual(generated_type._meta.name, expected_name)

    def test_relationship_field_types(self):
        """Test les types de champs de relation."""
        post_type = self.type_generator.generate_object_type(Post)
        fields = post_type._meta.fields

        # Vérifier le champ ForeignKey
        self.assertIn("category", fields)

        # Vérifier le champ ManyToMany
        self.assertIn("tags", fields)

    def test_type_registry(self):
        """Test le registre des types générés."""
        # Générer plusieurs types
        category_type = self.type_generator.generate_object_type(Category)
        post_type = self.type_generator.generate_object_type(Post)
        tag_type = self.type_generator.generate_object_type(Tag)

        # Vérifier que les types sont générés (registry is internal)
        self.assertIsNotNone(category_type)
        self.assertIsNotNone(post_type)
        self.assertIsNotNone(tag_type)


@pytest.mark.unit
class TestTypeGeneratorIntegration:
    """Tests d'intégration pour le générateur de types."""

    def test_full_type_generation_workflow(self):
        """Test le workflow complet de génération de types."""
        generator = TypeGenerator()

        # Générer un type complet
        category_type = generator.generate_object_type(Category)

        # Vérifier que le type est généré correctement
        assert category_type is not None
        assert hasattr(category_type, "_meta")
        assert category_type._meta.model == Category

        # Vérifier les champs de base
        fields = category_type._meta.fields
        assert "name" in fields
        assert "description" in fields

    def test_type_generation_with_relationships(self):
        """Test la génération de types avec relations."""
        generator = TypeGenerator()

        # Générer les types avec relations
        category_type = generator.generate_object_type(Category)
        post_type = generator.generate_object_type(Post)
        tag_type = generator.generate_object_type(Tag)

        # Vérifier que tous les types sont générés
        assert category_type is not None
        assert post_type is not None
        assert tag_type is not None

        # Vérifier les relations dans le type Post
        post_fields = post_type._meta.fields
        assert "category" in post_fields
        assert "tags" in post_fields

    def test_type_consistency_across_generations(self):
        """Test la cohérence des types à travers plusieurs générations."""
        generator = TypeGenerator()

        # Générer le même type plusieurs fois
        type1 = generator.generate_object_type(Category)
        type2 = generator.generate_object_type(Category)
        type3 = generator.generate_object_type(Category)

        # Vérifier que c'est le même type (cache)
        assert type1 is type2
        assert type2 is type3

        # Vérifier la cohérence des métadonnées
        assert type1._meta.model == type2._meta.model == type3._meta.model
        assert type1._meta.name == type2._meta.name == type3._meta.name


class TestAdvancedTypeGeneration(TestCase):
    """Tests avancés pour la génération de types."""

    def setUp(self):
        """Configuration des tests."""
        self.type_generator = TypeGenerator()

    def test_field_description_generation(self):
        """Test la génération des descriptions de champs."""
        category_type = self.type_generator.generate_object_type(Category)

        # Vérifier que les descriptions sont générées à partir des help_text
        # Note: Ceci dépend de l'implémentation spécifique du générateur
        self.assertIsNotNone(category_type)

    def test_type_inheritance(self):
        """Test la gestion de l'héritage de modèles."""

        # Créer un modèle qui hérite
        class TestExtendedCategory(Category):
            niveau_experience = models.IntegerField(
                default=1, verbose_name="Niveau d'expérience"
            )

            class Meta:
                app_label = "test_app"

        # Générer le type pour le modèle étendu
        extended_type = self.type_generator.generate_object_type(TestExtendedCategory)

        # Vérifier que le type inclut les champs hérités
        fields = extended_type._meta.fields
        self.assertIn("name", fields)  # Champ hérité
        self.assertIn("niveau_experience", fields)  # Nouveau champ
