// 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 Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Tasks;
using Microsoft.Build.Utilities;
using Shouldly;
using Xunit;
using Xunit.Abstractions;

#nullable disable

namespace Microsoft.Build.UnitTests.ResolveAssemblyReference_Tests.VersioningAndUnification.Prerequisite
{
    public sealed class StronglyNamedDependency : ResolveAssemblyReferenceTestFixture
    {
        public StronglyNamedDependency(ITestOutputHelper output) : base(output)
        {
        }

        /// <summary>
        /// Return the default search paths.
        /// </summary>
        /// <value></value>
        internal new string[] DefaultPaths
        {
            get { return new string[] { s_myApp_V10Path, @"C:\Framework\Whidbey", @"C:\Framework\Everett" }; }
        }

        /// <summary>
        /// In this case,
        /// - A single reference to DependsOnEverettSystem was passed in.
        ///   - This assembly depends on version 1.0.5000.0 of System.DLL.
        /// - No app.config is passed in.
        /// - Version 1.0.5000.0 of System.dll exists.
        /// - Whidbey Version of System.dll exists.
        /// Expected:
        /// - The resulting System.dll returned should be Whidbey version.
        /// Rationale:
        /// We automatically unify FX dependencies.
        /// </summary>
        [Fact]
        public void Exists()
        {
            // This WriteLine is a hack.  On a slow machine, the Tasks unittest fails because remoting
            // times out the object used for remoting console writes.  Adding a write in the middle of
            // keeps remoting from timing out the object.
            Console.WriteLine("Performing VersioningAndUnification.Prerequisite.StronglyNamedDependency.Exists() test");

            // Create the engine.
            MockEngine engine = new MockEngine(_output);

            ITaskItem[] assemblyNames = new TaskItem[]
            {
                new TaskItem("DependsOnEverettSystem, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=feedbeadbadcadbe")
            };

            // Now, pass feed resolved primary references into ResolveAssemblyReference.
            ResolveAssemblyReference t = new ResolveAssemblyReference();

            t.BuildEngine = engine;
            t.Assemblies = assemblyNames;
            t.SearchPaths = DefaultPaths;

            bool succeeded = Execute(t);

            Assert.True(succeeded);
            Assert.Single(t.ResolvedDependencyFiles);
            Assert.Equal(0, engine.Errors);
            Assert.Equal(0, engine.Warnings);
            t.ResolvedDependencyFiles[0].GetMetadata("FusionName")
                .ShouldBe("System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=" + AssemblyRef.EcmaPublicKey, StringCompareShould.IgnoreCase);

            engine.AssertLogContains(
                    String.Format(AssemblyResources.GetString("ResolveAssemblyReference.UnificationByFrameworkRetarget"), "1.0.5000.0", Path.Combine(s_myApp_V10Path, "DependsOnEverettSystem.dll")));

            engine.AssertLogContains(
                    String.Format(AssemblyResources.GetString("ResolveAssemblyReference.NotCopyLocalBecausePrerequisite")));

            t.ResolvedDependencyFiles[0].GetMetadata("CopyLocal").ShouldBe("false", StringCompareShould.IgnoreCase);
        }

        /// <summary>
        /// In this case,
        /// - A single reference to DependsOnEverettSystem was passed in.
        ///   - This assembly depends on version 1.0.5000.0 of System.DLL.
        /// - No app.config is passed in.
        /// - Version 1.0.5000.0 of System.dll exists.
        /// - Whidbey Version of System.dll *does not* exist.
        /// Expected:
        /// - This should be an unresolved reference, we shouldn't fallback to the old version.
        /// Rationale:
        /// The fusion loader is going to want to respect the unified-to assembly. There's no point in
        /// feeding it the wrong version, and the drawback is that builds would be different from 
        /// machine-to-machine.
        /// </summary>
        [Fact]
        public void HighVersionDoesntExist()
        {
            // Create the engine.
            MockEngine engine = new MockEngine(_output);

            ITaskItem[] assemblyNames = new TaskItem[]
            {
                new TaskItem("DependsOnEverettSystem, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=feedbeadbadcadbe")
            };

            // Now, pass feed resolved primary references into ResolveAssemblyReference.
            ResolveAssemblyReference t = new ResolveAssemblyReference();

            t.BuildEngine = engine;
            t.Assemblies = assemblyNames;
            t.SearchPaths = new string[] { s_myApp_V10Path, @"C:\Framework\Everett" };

            bool succeeded = Execute(t);

            Assert.True(succeeded);
            Assert.Empty(t.ResolvedDependencyFiles);
            engine.AssertLogContains(
                    String.Format(AssemblyResources.GetString("ResolveAssemblyReference.UnificationByFrameworkRetarget"), "1.0.5000.0", Path.Combine(s_myApp_V10Path, "DependsOnEverettSystem.dll")));
        }

        [Fact]
        public void VerifyAssemblyPulledOutOfFrameworkDoesntGetFrameworkFileAttribute()
        {
            MockEngine e = new MockEngine(_output);

            string actualFrameworkDirectory = s_myVersion20Path;
            string alternativeFrameworkDirectory = s_myVersion40Path;

            ITaskItem[] items = new TaskItem[] { new TaskItem(Path.Combine(actualFrameworkDirectory, "System.dll")) };

            // Version and directory match framework - it is a framework assembly
            string redistString1 = "<FileList Redist='Microsoft-Windows-CLRCoreComp-Random' >" +
                                   "<File AssemblyName='System' Version='2.0.0.0' PublicKeyToken='b77a5c561934e089' Culture='neutral' ProcessorArchitecture='MSIL' FileVersion='4.0.0.0' InGAC='true' />" +
                                   "</FileList >";

            ResolveAssemblyReference t1 = new ResolveAssemblyReference();
            t1.TargetFrameworkVersion = "v4.5";
            t1.TargetFrameworkDirectories = new string[] { actualFrameworkDirectory };
            ExecuteRAROnItemsAndRedist(t1, e, items, redistString1, true, new List<string>() { "{RawFileName}" });

            Assert.False(String.IsNullOrEmpty(t1.ResolvedFiles[0].GetMetadata("FrameworkFile")));

            // Higher version than framework, but directory matches - it is a framework assembly
            string redistString2 = "<FileList Redist='Microsoft-Windows-CLRCoreComp-Random' >" +
                                   "<File AssemblyName='System' Version='1.0.0.0' PublicKeyToken='b77a5c561934e089' Culture='neutral' ProcessorArchitecture='MSIL' FileVersion='4.0.0.0' InGAC='true' />" +
                                   "</FileList >";

            ResolveAssemblyReference t2 = new ResolveAssemblyReference();
            t2.TargetFrameworkVersion = "v4.5";
            t2.TargetFrameworkDirectories = new string[] { actualFrameworkDirectory };
            ExecuteRAROnItemsAndRedist(t2, e, items, redistString2, true, new List<string>() { "{RawFileName}" });

            Assert.False(String.IsNullOrEmpty(t2.ResolvedFiles[0].GetMetadata("FrameworkFile")));

            // Version is lower but directory does not match - it is a framework assembly
            string redistString3 = "<FileList Redist='Microsoft-Windows-CLRCoreComp-Random' >" +
                                   "<File AssemblyName='System' Version='3.0.0.0' PublicKeyToken='b77a5c561934e089' Culture='neutral' ProcessorArchitecture='MSIL' FileVersion='4.0.0.0' InGAC='true' />" +
                                   "</FileList >";

            ResolveAssemblyReference t3 = new ResolveAssemblyReference();
            t3.TargetFrameworkVersion = "v4.5";
            t3.TargetFrameworkDirectories = new string[] { alternativeFrameworkDirectory };
            ExecuteRAROnItemsAndRedist(t3, e, items, redistString3, true, new List<string>() { "{RawFileName}" });

            Assert.False(String.IsNullOrEmpty(t3.ResolvedFiles[0].GetMetadata("FrameworkFile")));

            // Version is higher and directory does not match - this assembly has been pulled out of .NET
            string redistString4 = "<FileList Redist='Microsoft-Windows-CLRCoreComp-Random' >" +
                                   "<File AssemblyName='System' Version='1.0.0.0' PublicKeyToken='b77a5c561934e089' Culture='neutral' ProcessorArchitecture='MSIL' FileVersion='4.0.0.0' InGAC='true' />" +
                                   "</FileList >";

            ResolveAssemblyReference t4 = new ResolveAssemblyReference();
            t4.TargetFrameworkVersion = "v4.5";
            t4.TargetFrameworkDirectories = new string[] { alternativeFrameworkDirectory };
            ExecuteRAROnItemsAndRedist(t4, e, items, redistString4, true, new List<string>() { "{RawFileName}" });

            Assert.True(String.IsNullOrEmpty(t4.ResolvedFiles[0].GetMetadata("FrameworkFile")));
        }
    }
}
