using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using ZB.MOM.WW.CBDD.SourceGenerators.Helpers;
using ZB.MOM.WW.CBDD.SourceGenerators.Models;

namespace ZB.MOM.WW.CBDD.SourceGenerators
{
    public class DbContextInfo
    {
        /// <summary>
        /// Gets or sets the simple class name of the DbContext.
        /// </summary>
        public string ClassName { get; set; } = "";

        /// <summary>
        /// Gets the fully qualified class name of the DbContext.
        /// </summary>
        public string FullClassName => string.IsNullOrEmpty(Namespace) ? ClassName : $"{Namespace}.{ClassName}";

        /// <summary>
        /// Gets or sets the namespace that contains the DbContext.
        /// </summary>
        public string Namespace { get; set; } = "";

        /// <summary>
        /// Gets or sets the source file path where the DbContext was found.
        /// </summary>
        public string FilePath { get; set; } = "";

        /// <summary>
        /// Gets or sets a value indicating whether the DbContext is nested.
        /// </summary>
        public bool IsNested { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the DbContext is partial.
        /// </summary>
        public bool IsPartial { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the DbContext inherits from another DbContext.
        /// </summary>
        public bool HasBaseDbContext { get; set; } // True if inherits from another DbContext (not DocumentDbContext directly)

        /// <summary>
        /// Gets or sets the entities discovered for this DbContext.
        /// </summary>
        public List<EntityInfo> Entities { get; set; } = new List<EntityInfo>();

        /// <summary>
        /// Gets or sets the collected nested types keyed by full type name.
        /// </summary>
        public Dictionary<string, NestedTypeInfo> GlobalNestedTypes { get; set; } = new Dictionary<string, NestedTypeInfo>();
    }

    [Generator]
    public class MapperGenerator : IIncrementalGenerator
    {
        /// <summary>
        /// Initializes the mapper source generator pipeline.
        /// </summary>
        /// <param name="context">The incremental generator initialization context.</param>
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
            // Find all classes that inherit from DocumentDbContext
            var dbContextClasses = context.SyntaxProvider
                .CreateSyntaxProvider(
                    predicate: static (node, _) => IsPotentialDbContext(node),
                    transform: static (ctx, _) => GetDbContextInfo(ctx))
                .Where(static context => context is not null)
                .Collect()
                .SelectMany(static (contexts, _) => contexts.GroupBy(c => c!.FullClassName).Select(g => g.First())!);

            // Generate code for each DbContext
            context.RegisterSourceOutput(dbContextClasses, static (spc, dbContext) =>
            {
                if (dbContext == null) return;

                var sb = new StringBuilder();
                sb.AppendLine($"// Found DbContext: {dbContext.ClassName}");
                sb.AppendLine($"// BaseType: {(dbContext.HasBaseDbContext ? "inherits from another DbContext" : "inherits from DocumentDbContext directly")}");


                foreach (var entity in dbContext.Entities)
                {
                    // Aggregate nested types recursively
                    CollectNestedTypes(entity.NestedTypes, dbContext.GlobalNestedTypes);
                }

                // Collect namespaces
                var namespaces = new HashSet<string>
                {
                    "System",
                    "System.Collections.Generic",
                    "ZB.MOM.WW.CBDD.Bson",
                    "ZB.MOM.WW.CBDD.Core.Collections"
                };

                // Add Entity namespaces
                foreach (var entity in dbContext.Entities)
                {
                    if (!string.IsNullOrEmpty(entity.Namespace))
                        namespaces.Add(entity.Namespace);
                }
                foreach (var nested in dbContext.GlobalNestedTypes.Values)
                {
                    if (!string.IsNullOrEmpty(nested.Namespace))
                        namespaces.Add(nested.Namespace);
                }

                // Sanitize file path for name uniqueness
                var safeName = dbContext.ClassName;
                if (!string.IsNullOrEmpty(dbContext.FilePath))
                {
                    var fileName = System.IO.Path.GetFileNameWithoutExtension(dbContext.FilePath);
                    safeName += $"_{fileName}";
                }

                sb.AppendLine("// <auto-generated/>");
                sb.AppendLine("#nullable enable");
                foreach (var ns in namespaces.OrderBy(n => n))
                {
                    sb.AppendLine($"using {ns};");
                }
                sb.AppendLine();

                // Use safeName (Context + Filename) to avoid collisions
                var mapperNamespace = $"{dbContext.Namespace}.{safeName}_Mappers";
                sb.AppendLine($"namespace {mapperNamespace}");
                sb.AppendLine($"{{");

                var generatedMappers = new HashSet<string>();

                // Generate Entity Mappers
                foreach (var entity in dbContext.Entities)
                {
                    if (generatedMappers.Add(entity.FullTypeName))
                    {
                        sb.AppendLine(CodeGenerator.GenerateMapperClass(entity, mapperNamespace));
                    }
                }

                // Generate Nested Mappers
                foreach (var nested in dbContext.GlobalNestedTypes.Values)
                {
                    if (generatedMappers.Add(nested.FullTypeName))
                    {
                        var nestedEntity = new EntityInfo
                        {
                            Name = nested.Name,
                            Namespace = nested.Namespace,
                            FullTypeName = nested.FullTypeName, // Ensure FullTypeName is copied
                            // Helper to copy properties
                        };
                        nestedEntity.Properties.AddRange(nested.Properties);

                        sb.AppendLine(CodeGenerator.GenerateMapperClass(nestedEntity, mapperNamespace));
                    }
                }

                sb.AppendLine($"}}");
                sb.AppendLine();

                // Partial DbContext for InitializeCollections (Only for top-level partial classes)
                if (!dbContext.IsNested && dbContext.IsPartial)
                {
                    sb.AppendLine($"namespace {dbContext.Namespace}");
                    sb.AppendLine($"{{");
                    sb.AppendLine($"    public partial class {dbContext.ClassName}");
                    sb.AppendLine($"    {{");
                    sb.AppendLine($"        protected override void InitializeCollections()");
                    sb.AppendLine($"        {{");

                    // Call base.InitializeCollections() if this context inherits from another DbContext
                    if (dbContext.HasBaseDbContext)
                    {
                        sb.AppendLine($"            base.InitializeCollections();");
                    }

                    foreach (var entity in dbContext.Entities)
                    {
                        if (!string.IsNullOrEmpty(entity.CollectionPropertyName))
                        {
                            var mapperName = $"global::{mapperNamespace}.{CodeGenerator.GetMapperName(entity.FullTypeName)}";
                            sb.AppendLine($"            this.{entity.CollectionPropertyName} = CreateCollection(new {mapperName}());");
                        }
                    }

                    sb.AppendLine($"        }}");
                    sb.AppendLine();

                    // Generate Set<TId, T>() override
                    var collectionsWithProperties = dbContext.Entities
                        .Where(e => !string.IsNullOrEmpty(e.CollectionPropertyName) && !string.IsNullOrEmpty(e.CollectionIdTypeFullName))
                        .ToList();

                    if (collectionsWithProperties.Any())
                    {
                        sb.AppendLine($"        public override global::ZB.MOM.WW.CBDD.Core.Collections.DocumentCollection<TId, T> Set<TId, T>()");
                        sb.AppendLine($"        {{");

                        foreach (var entity in collectionsWithProperties)
                        {
                            var entityTypeStr = $"global::{entity.FullTypeName}";
                            var idTypeStr = entity.CollectionIdTypeFullName;
                            sb.AppendLine($"            if (typeof(TId) == typeof({idTypeStr}) && typeof(T) == typeof({entityTypeStr}))");
                            sb.AppendLine($"                return (global::ZB.MOM.WW.CBDD.Core.Collections.DocumentCollection<TId, T>)(object)this.{entity.CollectionPropertyName};");
                        }

                        if (dbContext.HasBaseDbContext)
                        {
                            sb.AppendLine($"            return base.Set<TId, T>();");
                        }
                        else
                        {
                            sb.AppendLine($"            throw new global::System.InvalidOperationException($\"No collection registered for entity type '{{typeof(T).Name}}' with key type '{{typeof(TId).Name}}'.\");");
                        }

                        sb.AppendLine($"        }}");
                    }

                    sb.AppendLine($"    }}");
                    sb.AppendLine($"}}");
                }

                spc.AddSource($"{dbContext.Namespace}.{safeName}.Mappers.g.cs", sb.ToString());
            });
        }

        private static void CollectNestedTypes(Dictionary<string, NestedTypeInfo> source, Dictionary<string, NestedTypeInfo> target)
        {
            foreach (var kvp in source)
            {
                if (!target.ContainsKey(kvp.Value.FullTypeName))
                {
                    target[kvp.Value.FullTypeName] = kvp.Value;
                    CollectNestedTypes(kvp.Value.NestedTypes, target);
                }
            }
        }

        private static void PrintNestedTypes(StringBuilder sb, Dictionary<string, NestedTypeInfo> nestedTypes, string indent)
        {
            foreach (var nt in nestedTypes.Values)
            {
                sb.AppendLine($"//{indent}- {nt.Name} (Depth: {nt.Depth})");
                if (nt.Properties.Count > 0)
                {
                    // Print properties for nested type to be sure
                    foreach (var p in nt.Properties)
                    {
                        var flags = new List<string>();
                        if (p.IsCollection) flags.Add($"Collection<{p.CollectionItemType}>");
                        if (p.IsNestedObject) flags.Add($"Nested<{p.NestedTypeName}>");
                        var flagStr = flags.Any() ? $" [{string.Join(", ", flags)}]" : "";
                        sb.AppendLine($"//{indent}  - {p.Name}: {p.TypeName}{flagStr}");
                    }
                }

                if (nt.NestedTypes.Any())
                {
                    PrintNestedTypes(sb, nt.NestedTypes, indent + "  ");
                }
            }
        }

        private static bool IsPotentialDbContext(SyntaxNode node)
        {
            if (node.SyntaxTree.FilePath.EndsWith(".g.cs")) return false;

            return node is ClassDeclarationSyntax classDecl &&
                   classDecl.BaseList != null &&
                   classDecl.Identifier.Text.EndsWith("Context");
        }

        private static DbContextInfo? GetDbContextInfo(GeneratorSyntaxContext context)
        {
            var classDecl = (ClassDeclarationSyntax)context.Node;
            var semanticModel = context.SemanticModel;

            var classSymbol = semanticModel.GetDeclaredSymbol(classDecl) as INamedTypeSymbol;
            if (classSymbol == null) return null;

            if (!SyntaxHelper.InheritsFrom(classSymbol, "DocumentDbContext"))
                return null;

            // Check if this context inherits from another DbContext (not DocumentDbContext directly)
            var baseType = classSymbol.BaseType;
            bool hasBaseDbContext = baseType != null &&
                                    baseType.Name != "DocumentDbContext" &&
                                    SyntaxHelper.InheritsFrom(baseType, "DocumentDbContext");

            var info = new DbContextInfo
            {
                ClassName = classSymbol.Name,
                Namespace = classSymbol.ContainingNamespace.ToDisplayString(),
                FilePath = classDecl.SyntaxTree.FilePath,
                IsNested = classSymbol.ContainingType != null,
                IsPartial = classDecl.Modifiers.Any(m => m.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.PartialKeyword)),
                HasBaseDbContext = hasBaseDbContext
            };

            // Analyze OnModelCreating to find entities
            var onModelCreating = classDecl.Members
                .OfType<MethodDeclarationSyntax>()
                .FirstOrDefault(m => m.Identifier.Text == "OnModelCreating");

            if (onModelCreating != null)
            {
                var entityCalls = SyntaxHelper.FindMethodInvocations(onModelCreating, "Entity");
                foreach (var call in entityCalls)
                {
                    var typeName = SyntaxHelper.GetGenericTypeArgument(call);
                    if (typeName != null)
                    {
                        // Try to find the symbol
                        INamedTypeSymbol? entityType = null;

                        // 1. Try by name in current compilation (simple name)
                        var symbols = semanticModel.Compilation.GetSymbolsWithName(typeName);
                        entityType = symbols.OfType<INamedTypeSymbol>().FirstOrDefault();

                        // 2. Try by metadata name (if fully qualified)
                        if (entityType == null)
                        {
                            entityType = semanticModel.Compilation.GetTypeByMetadataName(typeName);
                        }

                        if (entityType != null)
                        {
                            // Check for duplicates
                            var fullTypeName = SyntaxHelper.GetFullName(entityType);
                            if (!info.Entities.Any(e => e.FullTypeName == fullTypeName))
                            {
                                var entityInfo = EntityAnalyzer.Analyze(entityType, semanticModel);
                                info.Entities.Add(entityInfo);
                            }
                        }
                    }
                }
            }

            // Analyze OnModelCreating for HasConversion
            if (onModelCreating != null)
            {
                var conversionCalls = SyntaxHelper.FindMethodInvocations(onModelCreating, "HasConversion");
                foreach (var call in conversionCalls)
                {
                    var converterName = SyntaxHelper.GetGenericTypeArgument(call);
                    if (converterName == null) continue;

                    // Trace back: .Property(x => x.Id).HasConversion<T>() or .HasKey(x => x.Id).HasConversion<T>()
                    if (call.Expression is MemberAccessExpressionSyntax { Expression: InvocationExpressionSyntax propertyCall } &&
                        propertyCall.Expression is MemberAccessExpressionSyntax { Name: IdentifierNameSyntax { Identifier: { Text: var propertyMethod } } } &&
                        (propertyMethod == "Property" || propertyMethod == "HasKey"))
                    {
                        var propertyName = SyntaxHelper.GetPropertyName(propertyCall.ArgumentList.Arguments.FirstOrDefault()?.Expression);
                        if (propertyName == null) continue;

                        // Trace further back: Entity<T>().Property(...)
                        if (propertyCall.Expression is MemberAccessExpressionSyntax { Expression: InvocationExpressionSyntax entityCall } &&
                            entityCall.Expression is MemberAccessExpressionSyntax { Name: GenericNameSyntax { Identifier: { Text: "Entity" } } })
                        {
                            var entityTypeName = SyntaxHelper.GetGenericTypeArgument(entityCall);
                            if (entityTypeName != null)
                            {
                                var entity = info.Entities.FirstOrDefault(e => e.Name == entityTypeName || e.FullTypeName.EndsWith("." + entityTypeName));
                                if (entity != null)
                                {
                                    var prop = entity.Properties.FirstOrDefault(p => p.Name == propertyName);
                                    if (prop != null)
                                    {
                                        // Resolve TProvider from ValueConverter<TModel, TProvider>
                                        var converterType = semanticModel.Compilation.GetTypeByMetadataName(converterName) ??
                                                           semanticModel.Compilation.GetSymbolsWithName(converterName).OfType<INamedTypeSymbol>().FirstOrDefault();

                                        prop.ConverterTypeName = converterType != null ? SyntaxHelper.GetFullName(converterType) : converterName;

                                        if (converterType != null && converterType.BaseType != null &&
                                            converterType.BaseType.Name == "ValueConverter" &&
                                            converterType.BaseType.TypeArguments.Length == 2)
                                        {
                                            prop.ProviderTypeName = converterType.BaseType.TypeArguments[1].Name;
                                        }
                                        else if (converterType != null)
                                        {
                                            // Fallback: search deeper in base types
                                            var converterBaseType = converterType.BaseType;
                                            while (converterBaseType != null)
                                            {
                                                if (converterBaseType.Name == "ValueConverter" && converterBaseType.TypeArguments.Length == 2)
                                                {
                                                    prop.ProviderTypeName = converterBaseType.TypeArguments[1].Name;
                                                    break;
                                                }
                                                converterBaseType = converterBaseType.BaseType;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            // Analyze properties to find DocumentCollection<TId, TEntity>
            var properties = classSymbol.GetMembers().OfType<IPropertySymbol>();
            foreach (var prop in properties)
            {
                if (prop.Type is INamedTypeSymbol namedType &&
                    namedType.OriginalDefinition.Name == "DocumentCollection")
                {
                    // Expecting 2 type arguments: TId, TEntity
                    if (namedType.TypeArguments.Length == 2)
                    {
                        var entityType = namedType.TypeArguments[1];
                        var entityInfo = info.Entities.FirstOrDefault(e => e.FullTypeName == entityType.ToDisplayString());

                        // If found, update
                        if (entityInfo != null)
                        {
                            entityInfo.CollectionPropertyName = prop.Name;
                            entityInfo.CollectionIdTypeFullName = namedType.TypeArguments[0].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
                        }
                    }
                }
            }

            return info;
        }
    }
}
