// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Razor.Test.Common;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.Extensions.DependencyModel;
using Xunit;

namespace Microsoft.CodeAnalysis;

public static class TestCompilation
{
    private static readonly ConcurrentDictionary<Assembly, IEnumerable<MetadataReference>> _referenceCache =
        new ConcurrentDictionary<Assembly, IEnumerable<MetadataReference>>();

    public static IEnumerable<MetadataReference> GetMetadataReferences(Assembly assembly)
    {
        var dependencyContext = DependencyContext.Load(assembly);

        var metadataReferences = dependencyContext.CompileLibraries
            .SelectMany(l => ResolvePaths(l))
            .Select(assemblyPath => MetadataReference.CreateFromFile(assemblyPath))
            .ToArray();

        return metadataReferences;
    }

    private static IEnumerable<string> ResolvePaths(CompilationLibrary library)
    {
#if NETFRAMEWORK
        var assemblies = AppDomain.CurrentDomain.GetAssemblies();
        for (var i = 0; i < assemblies.Length; i++)
        {
            if (assemblies[i].GetName().Name == library.Name)
            {
                return new[] { assemblies[i].Location };
            }
        }
#endif

        try
        {
            return library.ResolveReferencePaths();
        }
        catch (InvalidOperationException)
        {
        }

        return Array.Empty<string>();
    }

    public static string AssemblyName => "TestAssembly";

    // When we use Basic.Reference.Assemblies everywhere, this overload should be removed.
    // See also https://github.com/dotnet/razor/issues/10343.
    public static CSharpCompilation Create(Assembly assembly, SyntaxTree syntaxTree = null)
    {
        IEnumerable<SyntaxTree> syntaxTrees = null;

        if (syntaxTree != null)
        {
            syntaxTrees = new[] { syntaxTree };
        }

        if (!_referenceCache.TryGetValue(assembly, out var metadataReferences))
        {
            metadataReferences = GetMetadataReferences(assembly);
            _referenceCache.TryAdd(assembly, metadataReferences);
        }

        var compilation = CSharpCompilation.Create(AssemblyName, syntaxTrees, metadataReferences, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

        EnsureValidCompilation(compilation);

        return compilation;
    }

    public static CSharpCompilation Create() => Create(syntaxTrees: [], references: []);

    public static CSharpCompilation Create(IEnumerable<SyntaxTree> syntaxTrees, IEnumerable<MetadataReference> references)
    {
        var compilation = CSharpCompilation.Create(
            AssemblyName,
            syntaxTrees,
            [..references, ..ReferenceUtil.AspNetLatestAll],
            new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

        EnsureValidCompilation(compilation);

        return compilation;
    }

    private static void EnsureValidCompilation(CSharpCompilation compilation)
    {
        using (var stream = new MemoryStream())
        {
            var emitResult = compilation
                .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
                .Emit(stream);
            var diagnostics = string.Join(
                Environment.NewLine,
                emitResult.Diagnostics.Select(d => CSharpDiagnosticFormatter.Instance.Format(d)));
            Assert.True(emitResult.Success, $"Compilation is invalid : {Environment.NewLine}{diagnostics}");
        }
    }
}
