// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Microsoft.Oryx.BuildScriptGenerator;
using Microsoft.Oryx.BuildScriptGenerator.DotNetCore;
using Microsoft.Oryx.Detector.DotNetCore;
using Xunit;
using System.Collections.Generic;
using Microsoft.ApplicationInsights;
using Microsoft.Oryx.Tests.Common;

namespace Microsoft.Oryx.BuildScriptGenerator.Tests.DotNetCore
{
    public class DotNetCoreBuildScriptGenerationTest
    {
        
        private const string ProjectFileAzureBlazorWasmClientWithTargetFramework6 = @"
        <Project Sdk=""Microsoft.NET.Sdk.BlazorWebAssembly"">
          <PropertyGroup>
            <TargetFramework>net6.0</TargetFramework>
          </PropertyGroup>
          <ItemGroup>
            <PackageReference Include=""Microsoft.AspNetCore.Components.WebAssembly"" Version=""6.0.0-preview.3.21201.13"" />
            <PackageReference Include=""Microsoft.AspNetCore.Components.WebAssembly.DevServer"" Version=""6.0.0-preview.3.21201.13"" PrivateAssets=""all"" />
          </ItemGroup>
        </Project>
        ";

        [Fact]
        public void GeneratedBuildSnippet_AOTCompilationInstallCommandWillExecute_WhenBlazorWasmDotNet6App()
        {
            // Arrange
            var installationScript = "test-script";
            var dotNetCorePlatform = CreateDotNetCorePlatform(
                isDotNetCoreVersionAlreadyInstalled: true,
                dotNetCoreInstallationScript: installationScript);
            var repo = new MemorySourceRepo();
            repo.AddFile(ProjectFileAzureBlazorWasmClientWithTargetFramework6, "test.csproj");
            var context = CreateContext(repo);
            var detectedResult = new DotNetCorePlatformDetectorResult
            {
                Platform = DotNetCoreConstants.PlatformName,
                PlatformVersion = "6.0",
                InstallAOTWorkloads = true,
                ProjectFile = "test.csproj",
            };

            // Act
            var buildScriptSnippet = dotNetCorePlatform.GenerateBashBuildScriptSnippet(context, detectedResult);

            // Assert
            Assert.NotNull(buildScriptSnippet);
            Assert.Contains(DotNetCoreConstants.InstallBlazorWebAssemblyAOTWorkloadCommand,
                            buildScriptSnippet.BashBuildScriptSnippet);
        }

        [Fact]
        public void GeneratedBuildSnippet_CustomBuildCommandWillExecute_InsteadOfRestoreAndPublish()
        {
            // Arrange
            var installationScript = "test-script";
            var dotNetCorePlatform = CreateDotNetCorePlatform(
                DotNetCoreScriptGeneratorOptions: new DotNetCoreScriptGeneratorOptions
                {
                    CustomBuildCommand = "custom build command",
                },
                isDotNetCoreVersionAlreadyInstalled: true,
                dotNetCoreInstallationScript: installationScript);
            var repo = new MemorySourceRepo();
            repo.AddFile(ProjectFileAzureBlazorWasmClientWithTargetFramework6, "test.csproj");
            var context = CreateContext(repo);
            var detectedResult = new DotNetCorePlatformDetectorResult
            {
                Platform = DotNetCoreConstants.PlatformName,
                PlatformVersion = "10.0",
                ProjectFile = "test.csproj",
            };

            // Act
            var buildScriptSnippet = dotNetCorePlatform.GenerateBashBuildScriptSnippet(context, detectedResult);

            // Assert
            Assert.NotNull(buildScriptSnippet);
            Assert.Contains("custom build command", buildScriptSnippet.BashBuildScriptSnippet);
            Assert.DoesNotContain("cmd=\"dotnet restore", buildScriptSnippet.BashBuildScriptSnippet);
            Assert.DoesNotContain("cmd=\"dotnet publish", buildScriptSnippet.BashBuildScriptSnippet);
            // .NET always requires custom command to output to $DESTINATION_DIR directly
            Assert.False(buildScriptSnippet.CopySourceDirectoryContentToDestinationDirectory);
        }

        [Fact]
        public void GeneratedBuildSnippet_DoesNotCopySourceToDestination_WhenNoCustomBuildCommand()
        {
            // Arrange
            var dotNetCorePlatform = CreateDotNetCorePlatform(
                isDotNetCoreVersionAlreadyInstalled: true);
            var repo = new MemorySourceRepo();
            repo.AddFile(ProjectFileAzureBlazorWasmClientWithTargetFramework6, "test.csproj");
            var context = CreateContext(repo);
            var detectedResult = new DotNetCorePlatformDetectorResult
            {
                Platform = DotNetCoreConstants.PlatformName,
                PlatformVersion = "10.0",
                ProjectFile = "test.csproj",
            };

            // Act
            var buildScriptSnippet = dotNetCorePlatform.GenerateBashBuildScriptSnippet(context, detectedResult);

            // Assert - default dotnet publish handles output, so no auto-copy needed
            Assert.NotNull(buildScriptSnippet);
            Assert.False(buildScriptSnippet.CopySourceDirectoryContentToDestinationDirectory);
        }

        [Fact]
        public void GeneratedBuildSnippet_SdkVersionDisplayIsPreserved_WithCustomBuildCommand()
        {
            // Arrange
            var dotNetCorePlatform = CreateDotNetCorePlatform(
                DotNetCoreScriptGeneratorOptions: new DotNetCoreScriptGeneratorOptions
                {
                    CustomBuildCommand = "make build",
                },
                isDotNetCoreVersionAlreadyInstalled: true);
            var repo = new MemorySourceRepo();
            repo.AddFile(ProjectFileAzureBlazorWasmClientWithTargetFramework6, "test.csproj");
            var context = CreateContext(repo);
            var detectedResult = new DotNetCorePlatformDetectorResult
            {
                Platform = DotNetCoreConstants.PlatformName,
                PlatformVersion = "10.0",
                ProjectFile = "test.csproj",
            };

            // Act
            var buildScriptSnippet = dotNetCorePlatform.GenerateBashBuildScriptSnippet(context, detectedResult);

            // Assert - ensure dotnet env setup and display is preserved even when custom build command is used
            Assert.NotNull(buildScriptSnippet);
            Assert.Contains("dotnet --version", buildScriptSnippet.BashBuildScriptSnippet);
            Assert.Contains("make build", buildScriptSnippet.BashBuildScriptSnippet);
        }

        [Fact]
        public void GeneratedBuildSnippet_UsesDotNetRestoreAndPublish_WhenNoCustomBuildCommandSet()
        {
            // Arrange
            var dotNetCorePlatform = CreateDotNetCorePlatform(
                isDotNetCoreVersionAlreadyInstalled: true);
            var repo = new MemorySourceRepo();
            repo.AddFile(ProjectFileAzureBlazorWasmClientWithTargetFramework6, "test.csproj");
            var context = CreateContext(repo);
            var detectedResult = new DotNetCorePlatformDetectorResult
            {
                Platform = DotNetCoreConstants.PlatformName,
                PlatformVersion = "10.0",
                ProjectFile = "test.csproj",
            };

            // Act
            var buildScriptSnippet = dotNetCorePlatform.GenerateBashBuildScriptSnippet(context, detectedResult);

            // Assert
            Assert.NotNull(buildScriptSnippet);
            Assert.Contains("cmd=\"dotnet restore", buildScriptSnippet.BashBuildScriptSnippet);
            Assert.Contains("cmd=\"dotnet publish", buildScriptSnippet.BashBuildScriptSnippet);
            Assert.DoesNotContain("Running custom build command", buildScriptSnippet.BashBuildScriptSnippet);
        }

        [Fact]
        public void GeneratedBuildSnippet_AOTWorkloadIsPreserved_WithCustomBuildCommand()
        {
            // Arrange
            var dotNetCorePlatform = CreateDotNetCorePlatform(
                DotNetCoreScriptGeneratorOptions: new DotNetCoreScriptGeneratorOptions
                {
                    CustomBuildCommand = "dotnet publish -c Release -o /output",
                },
                isDotNetCoreVersionAlreadyInstalled: true);
            var repo = new MemorySourceRepo();
            repo.AddFile(ProjectFileAzureBlazorWasmClientWithTargetFramework6, "test.csproj");
            var context = CreateContext(repo);
            var detectedResult = new DotNetCorePlatformDetectorResult
            {
                Platform = DotNetCoreConstants.PlatformName,
                PlatformVersion = "10.0",
                InstallAOTWorkloads = true,
                ProjectFile = "test.csproj",
            };

            // Act
            var buildScriptSnippet = dotNetCorePlatform.GenerateBashBuildScriptSnippet(context, detectedResult);

            // Assert
            Assert.NotNull(buildScriptSnippet);
            Assert.Contains(DotNetCoreConstants.InstallBlazorWebAssemblyAOTWorkloadCommand, buildScriptSnippet.BashBuildScriptSnippet);
            Assert.Contains("dotnet publish -c Release -o /output", buildScriptSnippet.BashBuildScriptSnippet);
            Assert.DoesNotContain("dotnet restore", buildScriptSnippet.BashBuildScriptSnippet);
        }

        private BuildScriptGeneratorContext CreateContext(ISourceRepo sourceRepo = null)
        {
            sourceRepo = sourceRepo ?? new MemorySourceRepo();

            return new BuildScriptGeneratorContext
            {
                SourceRepo = sourceRepo,
            };
        }

        private DotNetCorePlatform CreateDotNetCorePlatform(
            Dictionary<string, string> supportedDotNetCoreVersions = null,
            string defaultVersion = null,
            string detectedVersion = null,
            BuildScriptGeneratorOptions commonOptions = null,
            DotNetCoreScriptGeneratorOptions DotNetCoreScriptGeneratorOptions = null,
            bool? isDotNetCoreVersionAlreadyInstalled = null,
            string DotNetCoreInstallationScript = null,
            string dotNetCoreInstallationScript = null)
        {
            commonOptions = commonOptions ?? new BuildScriptGeneratorOptions();
            DotNetCoreScriptGeneratorOptions = DotNetCoreScriptGeneratorOptions ?? new DotNetCoreScriptGeneratorOptions();
            isDotNetCoreVersionAlreadyInstalled = isDotNetCoreVersionAlreadyInstalled ?? true;
            DotNetCoreInstallationScript = DotNetCoreInstallationScript ?? "default-DotNetCore-installation-script";
            var versionProvider = new TestDotNetCoreVersionProvider(supportedDotNetCoreVersions, defaultVersion);
            var externalSdkProvider = new TestExternalSdkProvider();
            var detector = new TestDotNetCorePlatformDetector(detectedVersion: detectedVersion);
            var DotNetCoreInstaller = new TestDotNetCorePlatformInstaller(
                Options.Create(commonOptions),
                isDotNetCoreVersionAlreadyInstalled.Value,
                DotNetCoreInstallationScript);
            var globalJsonSdkResolver = new GlobalJsonSdkResolver(NullLogger<GlobalJsonSdkResolver>.Instance);
            return new TestDotNetCorePlatform(
                Options.Create(DotNetCoreScriptGeneratorOptions),
                Options.Create(commonOptions),
                versionProvider,
                new DotNetCoreExternalAcrVersionProvider(Options.Create(new BuildScriptGeneratorOptions()), NullLoggerFactory.Instance, new DefaultStandardOutputWriter()),
                NullLogger<TestDotNetCorePlatform>.Instance,
                detector,
                DotNetCoreInstaller,
                globalJsonSdkResolver,
                externalSdkProvider,
                new TestExternalAcrSdkProvider(),
                new TestAcrSdkProvider(),
                TelemetryClientHelper.GetTelemetryClient(),
                new DefaultStandardOutputWriter());
        }

        private class TestDotNetCorePlatform : DotNetCorePlatform
        {
            public TestDotNetCorePlatform(
                IOptions<DotNetCoreScriptGeneratorOptions> DotNetCoreScriptGeneratorOptions,
                IOptions<BuildScriptGeneratorOptions> commonOptions,
                IDotNetCoreVersionProvider DotNetCoreVersionProvider,
                DotNetCoreExternalAcrVersionProvider externalAcrVersionProvider,
                ILogger<DotNetCorePlatform> logger,
                IDotNetCorePlatformDetector detector,
                DotNetCorePlatformInstaller DotNetCoreInstaller,
                GlobalJsonSdkResolver globalJsonSdkResolver,
                IExternalSdkProvider externalSdkProvider,
                IExternalAcrSdkProvider externalAcrSdkProvider,
                IAcrSdkProvider acrSdkProvider,
                TelemetryClient telemetryClient,
                IStandardOutputWriter outputWriter)
                : base(
                      DotNetCoreVersionProvider,
                      externalAcrVersionProvider,
                      logger,
                      detector,
                      commonOptions,
                      DotNetCoreScriptGeneratorOptions,
                      DotNetCoreInstaller,
                      globalJsonSdkResolver,
                      externalSdkProvider,
                      externalAcrSdkProvider,
                      acrSdkProvider,
                      telemetryClient,
                      outputWriter)
            {
            }
        }

        private class TestDotNetCorePlatformInstaller : DotNetCorePlatformInstaller
        {
            private readonly bool _isVersionAlreadyInstalled;
            private readonly string _installerScript;

            public TestDotNetCorePlatformInstaller(
                IOptions<BuildScriptGeneratorOptions> commonOptions,
                bool isVersionAlreadyInstalled,
                string installerScript)
                : base(commonOptions, NullLoggerFactory.Instance)
            {
                _isVersionAlreadyInstalled = isVersionAlreadyInstalled;
                _installerScript = installerScript;
            }

            public override bool IsVersionAlreadyInstalled(string version)
            {
                return _isVersionAlreadyInstalled;
            }

            public override string GetInstallerScriptSnippet(string version, bool skipSdkBinaryDownload = false)
            {
                return _installerScript;
            }
        }
    }
}