using System.IO;
using System.Linq;
using UnityEditor;
using Microsoft.CodeAnalysis;
using UnityEditor.Compilation;
using System.Collections.Generic;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace IntelligentProjectAnalyzer.Analyzer
{
    /// <summary>
    /// A helper class that uses Roslyn to perform static analysis on C# scripts.
    /// </summary>
    public static class RoslynReferenceFinder
    {
        /// <summary>
        /// Finds references to a type or a method within that type.
        /// </summary>
        /// <param name="qualifier">A string that can be a class name, a full type name, or a full type name with a method.</param>
        /// <returns>A tuple containing the list of references, the GUID of the script where the type is defined, and the Type itself.</returns>
        public static (List<object> references, string foundScriptGuid, System.Type foundScriptType) FindReferences(string qualifier)
        {
            var (compilation, syntaxTrees) = GetProjectCompilation();
            var (typeSymbol, methodName) = ResolveQualifier(qualifier, compilation);

            if (typeSymbol == null)
            {
                return (new List<object>(), null, null);
            }
            
            var results = new List<object>();
            var classFullName = typeSymbol.ToDisplayString();

            foreach (var tree in syntaxTrees)
            {
                var semanticModel = compilation.GetSemanticModel(tree);
                var root = tree.GetRoot();
                
                var invocations = root.DescendantNodes().OfType<InvocationExpressionSyntax>();

                foreach (var invocation in invocations)
                {
                    if (semanticModel.GetSymbolInfo(invocation).Symbol is not IMethodSymbol methodSymbol) continue;

                    var isMatch = (methodName != null)
                        ? methodSymbol.Name == methodName && methodSymbol.ContainingType.ToDisplayString() == classFullName
                        : methodSymbol.ContainingType.ToDisplayString() == classFullName;

                    if (!isMatch) continue;
                    var location = tree.GetLineSpan(invocation.Span);
                    results.Add(new
                    {
                        path = tree.FilePath,
                        guid = AssetDatabase.AssetPathToGUID(tree.FilePath),
                        line = location.StartLinePosition.Line + 1,
                        invokedMethod = methodSymbol.Name,
                        text = invocation.ToString()
                    });
                }
            }
            
            var scriptPath = typeSymbol.DeclaringSyntaxReferences.FirstOrDefault()?.SyntaxTree.FilePath;
            var scriptGuid = !string.IsNullOrEmpty(scriptPath) ? AssetDatabase.AssetPathToGUID(scriptPath) : null;
            var scriptType = !string.IsNullOrEmpty(scriptGuid) ? AssetDatabase.LoadAssetAtPath<MonoScript>(scriptPath)?.GetClass() : null;

            return (results.Distinct().ToList(), scriptGuid, scriptType);
        }

        /// <summary>
        /// Intelligently parses the qualifier string to find the type symbol and an optional method name.
        /// </summary>
        private static (INamedTypeSymbol typeSymbol, string methodName) ResolveQualifier(string qualifier, CSharpCompilation compilation)
        {
            // First, try to resolve the whole string as a type.
            var typeSymbol = compilation.GetTypeByMetadataName(qualifier);
            if (typeSymbol != null)
            {
                return (typeSymbol, null); // Found a class/type, no method.
            }

            // If that fails, assume the last part is a method name.
            var lastDotIndex = qualifier.LastIndexOf('.');
            if (lastDotIndex == -1)
            {
                return (null, null); // Not a namespaced type or method.
            }

            var potentialTypeName = qualifier[..lastDotIndex];
            var potentialMethodName = qualifier[(lastDotIndex + 1)..];

            typeSymbol = compilation.GetTypeByMetadataName(potentialTypeName);
            if (typeSymbol == null) return (null, null); // Could not resolve.
            // Check if a method with that name actually exists on the type
            return typeSymbol.GetMembers(potentialMethodName).Any(m => m.Kind == SymbolKind.Method) ? 
                (typeSymbol, potentialMethodName) : (null, null); // Could not resolve.
        }

        private static (CSharpCompilation, List<SyntaxTree>) GetProjectCompilation()
        {
            var sourceFiles = AssetDatabase.FindAssets("t:MonoScript")
                .Select(AssetDatabase.GUIDToAssetPath)
                .Where(p => p.StartsWith("Assets/"))
                .ToList();
                
            var syntaxTrees = sourceFiles.Select(path => CSharpSyntaxTree.ParseText(File.ReadAllText(path), path: path)).ToList();

            var references = new List<MetadataReference>();
            foreach (var assembly in CompilationPipeline.GetAssemblies())
            {
                references.AddRange(assembly.compiledAssemblyReferences
                    .Select(p => MetadataReference.CreateFromFile(p)));
            }
            
            var compilation = CSharpCompilation.Create("ReferenceAnalysisAssembly", syntaxTrees, references);
            return (compilation, syntaxTrees);
        }
    }
}