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

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Internal.Microsoft.DotNet.PlatformAbstractions;
using Internal.Microsoft.Extensions.DependencyModel;
using Internal.Microsoft.Extensions.DependencyModel.Resolution;
using Xunit.Abstractions;

namespace Xunit
{
    class XunitPackageCompilationAssemblyResolver : ICompilationAssemblyResolver
    {
        readonly IFileSystem fileSystem;
        readonly List<string> nugetPackageDirectories;

        public XunitPackageCompilationAssemblyResolver(IMessageSink internalDiagnosticsMessageSink,
                                                       IFileSystem fileSystem = null)
        {
            nugetPackageDirectories = GetDefaultProbeDirectories(internalDiagnosticsMessageSink);
            this.fileSystem = fileSystem ?? new FileSystemWrapper();
        }

        static List<string> GetDefaultProbeDirectories(IMessageSink internalDiagnosticsMessageSink) =>
            GetDefaultProbeDirectories(RuntimeEnvironment.OperatingSystemPlatform, internalDiagnosticsMessageSink);

        static List<string> GetDefaultProbeDirectories(Platform osPlatform, IMessageSink internalDiagnosticsMessageSink)
        {
            var results = default(List<string>);

            var probeDirectories = AppContext.GetData("PROBING_DIRECTORIES") as string;
            if (!string.IsNullOrEmpty(probeDirectories))
                results = probeDirectories.Split(new char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries).ToList();
            else
            {
                results = new List<string>();

                // Allow the user to override the default location of NuGet packages
                var packageDirectory = Environment.GetEnvironmentVariable("NUGET_PACKAGES");
                if (!string.IsNullOrEmpty(packageDirectory))
                    results.Add(packageDirectory);
                else
                {
                    string basePath;
                    if (osPlatform == Platform.Windows)
                        basePath = Environment.GetEnvironmentVariable("USERPROFILE");
                    else
                        basePath = Environment.GetEnvironmentVariable("HOME");

                    if (!string.IsNullOrEmpty(basePath))
                        results.Add(Path.Combine(basePath, ".nuget", "packages"));
                }
            }

            if (internalDiagnosticsMessageSink != null)
                internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[XunitPackageCompilationAssemblyResolver.GetDefaultProbeDirectories] returns: [{string.Join(",", results.Select(x => $"'{x}'"))}]"));

            return results;
        }

        public bool TryResolveAssemblyPaths(CompilationLibrary library, List<string> assemblies)
        {
            if (nugetPackageDirectories.Count == 0 || !string.Equals(library.Type, "package", StringComparison.OrdinalIgnoreCase))
                return false;

            foreach (var directory in nugetPackageDirectories)
                if (ResolverUtils.TryResolvePackagePath(fileSystem, library, directory, out var packagePath))
                    if (TryResolveFromPackagePath(library, packagePath, out var fullPathsFromPackage))
                    {
                        assemblies.AddRange(fullPathsFromPackage);
                        return true;
                    }

            return false;
        }

        bool TryResolveFromPackagePath(CompilationLibrary library, string basePath, out IEnumerable<string> results)
        {
            var paths = new List<string>();

            foreach (var assembly in library.Assemblies)
            {
                if (!ResolverUtils.TryResolveAssemblyFile(fileSystem, basePath, assembly, out var fullName))
                {
                    // if one of the files can't be found, skip this package path completely.
                    // there are package paths that don't include all of the "ref" assemblies 
                    // (ex. ones created by 'dotnet store')
                    results = null;
                    return false;
                }

                paths.Add(fullName);
            }

            results = paths;
            return true;
        }
    }
}
