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

using Castle.Core.Internal;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Microsoft.Oryx.BuildScriptGenerator.Common;
using Microsoft.Oryx.BuildScriptGenerator.Exceptions;
using Microsoft.Oryx.BuildScriptGenerator.Golang;
using Microsoft.Oryx.BuildScriptGenerator.Php;
using Microsoft.Oryx.Detector;
using Microsoft.Oryx.Detector.Php;
using Microsoft.Oryx.Tests.Common;
using System.Linq;
using Xunit;

namespace Microsoft.Oryx.BuildScriptGenerator.Tests.Php
{
    public class PhpPlatformTest
    {
        [Theory]
        [InlineData("invalid json")]
        [InlineData("{\"data\": \"valid but meaningless\"}")]
        public void Detect_ReturnsResult_WithPhpDefaultVersion_WithComposerFile(string composerFileContent)
        {
            // Arrange
            var expectedVersion = "10.10.10";
            var platform = CreatePhpPlatform(
                supportedPhpVersions: new[] { expectedVersion },
                defaultVersion: expectedVersion);
            var repo = new MemorySourceRepo();
            repo.AddFile(composerFileContent, PhpConstants.ComposerFileName);
            repo.AddFile("<?php echo true; ?>", "foo.php");
            var context = CreateContext(repo);

            // Act
            var result = platform.Detect(context);

            // Assert
            Assert.NotNull(result);
            Assert.Equal(PhpConstants.PlatformName, result.Platform);
            Assert.Equal(expectedVersion, result.PlatformVersion);
        }

        [Theory]
        [InlineData(new[] { "8.11.2", "5.6.9", "8.11.13" }, "5.6.9", ">5", "8.11.13")]
        [InlineData(new[] { "8.9.5", "8.11.2", "5.6.9", "8.11.13" }, "5.6.9", ">8.9", "8.11.13")]
        [InlineData(new[] { "8.9.5", "8.11.2", "11.12.0", "13.12.12", "8.11.13" }, "5.6.9", ">8.9 <13", "11.12.0")]
        public void Detect_ReturnsVersionFromComposerFile_UsingMaximumSatisfyingVersionRules(
            string[] supportedVersions,
            string defaultVersion,
            string versionInComposerFile,
            string expectedVersion)
        {
            // Arrange
            var platform = CreatePhpPlatform(
                supportedPhpVersions: supportedVersions,
                defaultVersion: defaultVersion,
                detectedVersion: versionInComposerFile);
            var context = CreateContext();

            // Act
            var result = platform.Detect(context);

            // Assert
            Assert.NotNull(result);
            Assert.Equal(PhpConstants.PlatformName, result.Platform);
            Assert.Equal(expectedVersion, result.PlatformVersion);
        }

        [Theory]
        [InlineData(new[] { "8.11.2", "5.6.9", "8.11.13" }, "5.6.9", ">5", "8.11.13")]
        [InlineData(new[] { "8.9.5", "8.11.2", "5.6.9", "8.11.13" }, "5.6.9", ">8.9", "8.11.13")]
        [InlineData(new[] { "8.9.5", "8.11.2", "11.12.0", "13.12.12", "8.11.13" }, "5.6.9", ">8.9 <13", "11.12.0")]
        public void Detect_ReturnsVersionFromOptions_UsingMaximumSatisfyingVersionRules(
            string[] supportedVersions,
            string defaultVersion,
            string versionInOptions,
            string expectedVersion)
        {
            // Arrange
            var phpScriptGeneratorOptions = new PhpScriptGeneratorOptions
            {
                PhpVersion = versionInOptions
            };
            var platform = CreatePhpPlatform(
                supportedPhpVersions: supportedVersions,
                defaultVersion: defaultVersion,
                detectedVersion: "1.1.1",
                phpScriptGeneratorOptions: phpScriptGeneratorOptions);
            var context = CreateContext();

            // Act
            var result = platform.Detect(context);

            // Assert
            Assert.NotNull(result);
            Assert.Equal(PhpConstants.PlatformName, result.Platform);
            Assert.Equal(expectedVersion, result.PlatformVersion);
        }

        [Theory]
        [InlineData(new[] { "7.3.20RC1", "7.4.0beta4", "7.4.0RC6", "8.0.0alpha1" }, "7.4.0beta4", "7.4.0beta4")]
        [InlineData(new[] { "7.3.20RC1", "7.4.0beta4", "7.4.0RC6", "8.0.0alpha1" }, "7.3.20RC1", "7.3.20RC1")]
        [InlineData(new[] { "7.3.20RC1", "7.4.0beta4", "7.4.0RC6", "8.0.0alpha1" }, "8.0.0alpha1", "8.0.0alpha1")]
        public void Detect_ReturnsPreviewVersion_UsingMaximumSatisfyingVersionRules(
            string[] supportedVersions,
            string versionInComposerFile,
            string expectedVersion)
        {
            // Arrange
            var platform = CreatePhpPlatform(
                detectedVersion: versionInComposerFile,
                supportedPhpVersions: supportedVersions);
            var repo = new MemorySourceRepo();
            var context = CreateContext(repo);

            // Act
            var result = platform.Detect(context);

            // Assert
            Assert.NotNull(result);
            Assert.Equal(PhpConstants.PlatformName, result.Platform);
            Assert.Equal(expectedVersion, result.PlatformVersion);
        }

        [Theory]
        [InlineData(new[] { "7.3.20RC1", "7.4.0beta4", "7.4.0RC6", "8.0.0alpha1" }, "7.4.0beta4", "7.4.0beta4")]
        [InlineData(new[] { "7.3.20RC1", "7.4.0beta4", "7.4.0RC6", "8.0.0alpha1" }, "7.3.20RC1", "7.3.20RC1")]
        [InlineData(new[] { "7.3.20RC1", "7.4.0beta4", "7.4.0RC6", "8.0.0alpha1" }, "8.0.0alpha1", "8.0.0alpha1")]
        public void Detect_WhenPreviewPhpVersion_IsSetInOptions(string[] supportedVersions, string previewVersion, string expectedVersion)
        {
            // Arrange
            var phpScriptGeneratorOptions = new PhpScriptGeneratorOptions
            {
                PhpVersion = previewVersion
            };
            var platform = CreatePhpPlatform(
                supportedPhpVersions: supportedVersions,
                defaultVersion: previewVersion,
                phpScriptGeneratorOptions: phpScriptGeneratorOptions);
            var repo = new MemorySourceRepo();
            repo.AddFile("{\"require\":{\"php\":\"" + previewVersion + "\"}}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);

            // Act
            var result = platform.Detect(context);

            // Act & Assert
            Assert.NotNull(result);
            Assert.Equal(PhpConstants.PlatformName, result.Platform);
            Assert.Equal(expectedVersion, result.PlatformVersion);
        }

        [Fact]
        public void Detect_ReturnsVersionFromOptions_EvenIfDetectorReturnsVersion()
        {
            // Arrange
            var expectedVersion = "100.100.100";
            var detectedVersion = "5.6.0";
            var defaultVersion = "7.3.14";
            var phpScriptGeneratorOptions = new PhpScriptGeneratorOptions
            {
                PhpVersion = expectedVersion
            };
            var platform = CreatePhpPlatform(
                supportedPhpVersions: new[] { defaultVersion, "7.2.5", detectedVersion, expectedVersion },
                defaultVersion: defaultVersion,
                detectedVersion: detectedVersion,
                phpScriptGeneratorOptions: phpScriptGeneratorOptions);
            var context = CreateContext();

            // Act
            var result = platform.Detect(context);

            // Assert
            Assert.NotNull(result);
            Assert.Equal(PhpConstants.PlatformName, result.Platform);
            Assert.Equal(expectedVersion, result.PlatformVersion);
        }

        [Fact]
        public void Detect_Throws_WhenUnsupportedPhpVersion_ReturnedByDetector()
        {
            // Arrange
            var detectedVersion = "0";
            var supportedVersion = "7.3.5";
            var platform = CreatePhpPlatform(
                supportedPhpVersions: new[] { supportedVersion },
                defaultVersion: supportedVersion,
                detectedVersion: detectedVersion);
            var context = CreateContext();

            // Act & Assert
            var exception = Assert.Throws<UnsupportedVersionException>(() => platform.Detect(context));
            Assert.Equal(
                $"Platform 'php' version '{detectedVersion}' is unsupported. " +
                $"Supported versions: {supportedVersion}",
                exception.Message);
        }

        [Fact]
        public void Detect_Throws_WhenUnsupportedPhpVersion_IsSetInOptions()
        {
            // Arrange
            var versionFromOptions = "0";
            var supportedVersion = "7.3.5";
            var phpScriptGeneratorOptions = new PhpScriptGeneratorOptions
            {
                PhpVersion = versionFromOptions
            };
            var platform = CreatePhpPlatform(
                supportedPhpVersions: new[] { supportedVersion },
                defaultVersion: supportedVersion,
                detectedVersion: supportedVersion,
                phpScriptGeneratorOptions: phpScriptGeneratorOptions);
            var context = CreateContext();

            // Act & Assert
            var exception = Assert.Throws<UnsupportedVersionException>(() => platform.Detect(context));
            Assert.Equal(
                $"Platform 'php' version '{versionFromOptions}' is unsupported. " +
                $"Supported versions: {supportedVersion}",
                exception.Message);
        }

        [Fact]
        public void Detect_ReturnsDefaultVersion_IfNoVersionFoundReturnedByDetector_OrOptions()
        {
            // Arrange
            var expectedVersion = "7.3.14";
            var platform = CreatePhpPlatform(
                supportedPhpVersions: new[] { "5.6.0", expectedVersion, "7.2.5" },
                defaultVersion: expectedVersion,
                detectedVersion: null);
            var context = CreateContext();

            // Act
            var result = platform.Detect(context);

            // Assert
            Assert.NotNull(result);
            Assert.Equal(PhpConstants.PlatformName, result.Platform);
            Assert.Equal(expectedVersion, result.PlatformVersion);
        }

        [Fact]
        public void HasPhpInstallScript_IfDynamicInstallIsEnabled_AndPhpVersionIsNotAlreadyInstalled()
        {
            // Arrange
            var commonOptions = new BuildScriptGeneratorOptions();
            commonOptions.EnableDynamicInstall = true;
            var phpPlatform = CreatePhpPlatform(
                commonOptions: commonOptions,
                isPhpVersionAlreadyInstalled: false);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert
            Assert.NotNull(actualScriptSnippet);
            Assert.Contains(TestPhpPlatformInstaller.InstallerScript, actualScriptSnippet);
        }

        

        [Fact]
        public void PhpInstallViaExternalProvider_IfDynamicInstallAndExternalProviderIsEnabled_AndPhpVersionIsNotAlreadyInstalled()
        {
            // Arrange
            var commonOptions = new BuildScriptGeneratorOptions() {
                 EnableDynamicInstall = true,
                 EnableExternalSdkProvider = true,
                 DebianFlavor = OsTypes.DebianBookworm,
                };
            var phpPlatform = CreatePhpPlatform(
                commonOptions: commonOptions,
                isPhpVersionAlreadyInstalled: false);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert
            Assert.NotNull(actualScriptSnippet);
            Assert.Contains(TestPhpPlatformInstaller.InstallerScriptWithSkipSdkBinaryDownload, actualScriptSnippet);
        }

        [Fact]
        public void HasNoPhpInstallScript_IfDynamicInstallIsEnabled_AndPhpVersionIsAlreadyInstalled()
        {
            // Arrange
            var commonOptions = new BuildScriptGeneratorOptions();
            commonOptions.EnableDynamicInstall = true;
            var phpPlatform = CreatePhpPlatform(
                commonOptions: commonOptions,
                isPhpVersionAlreadyInstalled: true);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert
            Assert.Null(actualScriptSnippet);
        }

        [Fact]
        public void DoesNotHavePhpInstallScript_IfDynamicInstallNotEnabled_AndPhpVersionIsNotAlreadyInstalled()
        {
            // Arrange
            var commonOptions = new BuildScriptGeneratorOptions();
            commonOptions.EnableDynamicInstall = false;
            var phpPlatform = CreatePhpPlatform(
                commonOptions: commonOptions,
                isPhpVersionAlreadyInstalled: false);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert
            Assert.Null(actualScriptSnippet);
        }

        [Fact]
        public void HasPhpComposerInstallScript_IfDynamicInstallIsEnabled_AndPhpComposerVersionIsNotAlreadyInstalled()
        {
            // Arrange
            var commonOptions = new BuildScriptGeneratorOptions();
            commonOptions.EnableDynamicInstall = true;
            var phpPlatform = CreatePhpPlatform(
                commonOptions: commonOptions,
                isPhpComposerAlreadyInstalled: false);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert
            Assert.NotNull(actualScriptSnippet);
            Assert.Contains(TestPhpComposerInstaller.InstallerScript, actualScriptSnippet);
        }    

        [Fact]
        public void ComposerInstallViaExternalProvider_IfDynamicInstallAndExternalProviderIsEnabled_AndComposerVersionIsNotAlreadyInstalled()
        {
            // Arrange
            var commonOptions = new BuildScriptGeneratorOptions() {
                 EnableDynamicInstall = true,
                 EnableExternalSdkProvider = true,
                 DebianFlavor = OsTypes.DebianBookworm,
                };
            var phpPlatform = CreatePhpPlatform(
                commonOptions: commonOptions,
                isPhpComposerAlreadyInstalled: false);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert
            Assert.NotNull(actualScriptSnippet);
            Assert.Contains(TestPhpComposerInstaller.InstallerScriptWithSkipSdkBinaryDownload, actualScriptSnippet);
        }

        [Fact]
        public void HasNoPhpComposerInstallScript_IfDynamicInstallIsEnabled_AndPhpComposerVersionIsAlreadyInstalled()
        {
            // Arrange
            var commonOptions = new BuildScriptGeneratorOptions();
            commonOptions.EnableDynamicInstall = true;
            var phpPlatform = CreatePhpPlatform(
                commonOptions: commonOptions,
                isPhpComposerAlreadyInstalled: true);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert
            Assert.Null(actualScriptSnippet);
        }

        [Fact]
        public void DoesNotHavePhpComposerInstallScript_IfDynamicInstallNotEnabled_AndPhpComposerVersionIsNotAlreadyInstalled()
        {
            // Arrange
            var commonOptions = new BuildScriptGeneratorOptions();
            commonOptions.EnableDynamicInstall = false;
            var phpPlatform = CreatePhpPlatform(
                commonOptions: commonOptions,
                isPhpComposerAlreadyInstalled: false);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert
            Assert.Null(actualScriptSnippet);
        }

        [Fact]
        public void ComposerInstallViaExternalProvider_IfDynamicInstallIsEnabled_AndPhpAndComposerVersionIsNotAlreadyInstalled()
        {
            // Arrange
            var commonOptions = new BuildScriptGeneratorOptions() {
                 EnableDynamicInstall = true,
                 EnableExternalSdkProvider = true,
                 DebianFlavor = OsTypes.DebianBookworm,
                };
            var phpPlatform = CreatePhpPlatform(
                commonOptions: commonOptions,
                isPhpVersionAlreadyInstalled: false,
                isPhpComposerAlreadyInstalled: false);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert
            Assert.NotNull(actualScriptSnippet);
            Assert.Contains(TestPhpPlatformInstaller.InstallerScriptWithSkipSdkBinaryDownload, actualScriptSnippet);
            Assert.Contains(TestPhpComposerInstaller.InstallerScriptWithSkipSdkBinaryDownload, actualScriptSnippet);
        }

        [Fact]
        public void HasPhpAndComposerInstallScript_IfDynamicInstallIsEnabled_AndPhpAndComposerVersionIsNotAlreadyInstalled()
        {
            // Arrange
            var commonOptions = new BuildScriptGeneratorOptions();
            commonOptions.EnableDynamicInstall = true;
            var phpPlatform = CreatePhpPlatform(
                commonOptions: commonOptions,
                isPhpVersionAlreadyInstalled: false,
                isPhpComposerAlreadyInstalled: false);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert
            Assert.NotNull(actualScriptSnippet);
            Assert.Contains(TestPhpPlatformInstaller.InstallerScript, actualScriptSnippet);
            Assert.Contains(TestPhpComposerInstaller.InstallerScript, actualScriptSnippet);
        }

        [Fact]
        public void PhpInstallViaExternalAcrProvider_IfEnabled_AndPhpVersionIsNotAlreadyInstalled()
        {
            // Arrange
            var externalAcrSdkProvider = new TestExternalAcrSdkProvider(returnValue: true);
            var commonOptions = new BuildScriptGeneratorOptions()
            {
                EnableDynamicInstall = true,
                EnableExternalAcrSdkProvider = true,
                DebianFlavor = OsTypes.DebianBookworm,
            };
            var phpPlatform = CreatePhpPlatformWithProviders(
                commonOptions: commonOptions,
                isPhpVersionAlreadyInstalled: false,
                externalAcrSdkProvider: externalAcrSdkProvider);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert
            Assert.NotNull(actualScriptSnippet);
            Assert.Contains(TestPhpPlatformInstaller.InstallerScriptWithSkipSdkBinaryDownload, actualScriptSnippet);
            Assert.True(externalAcrSdkProvider.RequestSdkAsyncCalled);
        }

        [Fact]
        public void PhpInstallViaDirectAcrProvider_IfEnabled_AndPhpVersionIsNotAlreadyInstalled()
        {
            // Arrange
            var acrSdkProvider = new TestAcrSdkProvider(returnValue: true);
            var commonOptions = new BuildScriptGeneratorOptions()
            {
                EnableDynamicInstall = true,
                EnableAcrSdkProvider = true,
                DebianFlavor = OsTypes.DebianBookworm,
            };
            var phpPlatform = CreatePhpPlatformWithProviders(
                commonOptions: commonOptions,
                isPhpVersionAlreadyInstalled: false,
                acrSdkProvider: acrSdkProvider);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert
            Assert.NotNull(actualScriptSnippet);
            Assert.Contains(TestPhpPlatformInstaller.InstallerScriptWithSkipSdkBinaryDownload, actualScriptSnippet);
            Assert.True(acrSdkProvider.RequestSdkFromAcrAsyncCalled);
        }

        [Fact]
        public void PhpFallsBackFromExternalAcrToExternalSdk_WhenExternalAcrFails()
        {
            // Arrange
            var externalAcrSdkProvider = new TestExternalAcrSdkProvider(returnValue: false);
            var commonOptions = new BuildScriptGeneratorOptions()
            {
                EnableDynamicInstall = true,
                EnableExternalAcrSdkProvider = true,
                EnableExternalSdkProvider = true,
                DebianFlavor = OsTypes.DebianBookworm,
            };
            var phpPlatform = CreatePhpPlatformWithProviders(
                commonOptions: commonOptions,
                isPhpVersionAlreadyInstalled: false,
                externalAcrSdkProvider: externalAcrSdkProvider);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert
            Assert.NotNull(actualScriptSnippet);
            Assert.Contains(TestPhpPlatformInstaller.InstallerScriptWithSkipSdkBinaryDownload, actualScriptSnippet);
            Assert.True(externalAcrSdkProvider.RequestSdkAsyncCalled);
        }

        [Fact]
        public void PhpFallsBackToCdn_WhenAllProvidersFail()
        {
            // Arrange
            var externalAcrSdkProvider = new TestExternalAcrSdkProvider(returnValue: false);
            var acrSdkProvider = new TestAcrSdkProvider(returnValue: false);
            var externalSdkProvider = new TestExternalSdkProvider(requestBlobResult: false);
            var commonOptions = new BuildScriptGeneratorOptions()
            {
                EnableDynamicInstall = true,
                EnableExternalAcrSdkProvider = true,
                EnableExternalSdkProvider = true,
                EnableAcrSdkProvider = true,
                DebianFlavor = OsTypes.DebianBookworm,
            };
            var phpPlatform = CreatePhpPlatformWithProviders(
                commonOptions: commonOptions,
                isPhpVersionAlreadyInstalled: false,
                externalAcrSdkProvider: externalAcrSdkProvider,
                acrSdkProvider: acrSdkProvider,
                externalSdkProvider: externalSdkProvider);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert
            Assert.NotNull(actualScriptSnippet);
            Assert.Contains(TestPhpPlatformInstaller.InstallerScript, actualScriptSnippet);
            Assert.True(externalAcrSdkProvider.RequestSdkAsyncCalled);
            Assert.True(acrSdkProvider.RequestSdkFromAcrAsyncCalled);
        }

        [Fact]
        public void PhpFallsBackFromExternalAcrToDirectAcr_WhenBothExternalProvidersFail()
        {
            // Arrange
            var externalAcrSdkProvider = new TestExternalAcrSdkProvider(returnValue: false);
            var acrSdkProvider = new TestAcrSdkProvider(returnValue: true);
            var externalSdkProvider = new TestExternalSdkProvider(requestBlobResult: false);
            var commonOptions = new BuildScriptGeneratorOptions()
            {
                EnableDynamicInstall = true,
                EnableExternalAcrSdkProvider = true,
                EnableExternalSdkProvider = true,
                EnableAcrSdkProvider = true,
                DebianFlavor = OsTypes.DebianBookworm,
            };
            var phpPlatform = CreatePhpPlatformWithProviders(
                commonOptions: commonOptions,
                isPhpVersionAlreadyInstalled: false,
                externalAcrSdkProvider: externalAcrSdkProvider,
                acrSdkProvider: acrSdkProvider,
                externalSdkProvider: externalSdkProvider);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert
            Assert.NotNull(actualScriptSnippet);
            Assert.Contains(TestPhpPlatformInstaller.InstallerScriptWithSkipSdkBinaryDownload, actualScriptSnippet);
            Assert.True(externalAcrSdkProvider.RequestSdkAsyncCalled);
            Assert.True(acrSdkProvider.RequestSdkFromAcrAsyncCalled);
        }

        [Fact]
        public void ComposerInstallViaExternalAcrProvider_IfEnabled_AndComposerVersionIsNotAlreadyInstalled()
        {
            // Arrange
            var externalAcrSdkProvider = new TestExternalAcrSdkProvider(returnValue: true);
            var commonOptions = new BuildScriptGeneratorOptions()
            {
                EnableDynamicInstall = true,
                EnableExternalAcrSdkProvider = true,
                DebianFlavor = OsTypes.DebianBookworm,
            };
            var phpPlatform = CreatePhpPlatformWithProviders(
                commonOptions: commonOptions,
                isPhpVersionAlreadyInstalled: false,
                isPhpComposerAlreadyInstalled: false,
                externalAcrSdkProvider: externalAcrSdkProvider);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert
            Assert.NotNull(actualScriptSnippet);
            Assert.Contains(TestPhpPlatformInstaller.InstallerScriptWithSkipSdkBinaryDownload, actualScriptSnippet);
            Assert.Contains(TestPhpComposerInstaller.InstallerScriptWithSkipSdkBinaryDownload, actualScriptSnippet);
        }

        [Fact]
        public void ComposerInstallViaDirectAcrProvider_IfEnabled_AndComposerVersionIsNotAlreadyInstalled()
        {
            // Arrange
            var acrSdkProvider = new TestAcrSdkProvider(returnValue: true);
            var commonOptions = new BuildScriptGeneratorOptions()
            {
                EnableDynamicInstall = true,
                EnableAcrSdkProvider = true,
                DebianFlavor = OsTypes.DebianBookworm,
            };
            var phpPlatform = CreatePhpPlatformWithProviders(
                commonOptions: commonOptions,
                isPhpVersionAlreadyInstalled: false,
                isPhpComposerAlreadyInstalled: false,
                acrSdkProvider: acrSdkProvider);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert
            Assert.NotNull(actualScriptSnippet);
            Assert.Contains(TestPhpPlatformInstaller.InstallerScriptWithSkipSdkBinaryDownload, actualScriptSnippet);
            Assert.Contains(TestPhpComposerInstaller.InstallerScriptWithSkipSdkBinaryDownload, actualScriptSnippet);
        }

        [Fact]
        public void ComposerFallsBackToCdn_WhenAllProvidersFail()
        {
            // Arrange
            var externalAcrSdkProvider = new TestExternalAcrSdkProvider(returnValue: false);
            var acrSdkProvider = new TestAcrSdkProvider(returnValue: false);
            var externalSdkProvider = new TestExternalSdkProvider(requestBlobResult: false);
            var commonOptions = new BuildScriptGeneratorOptions()
            {
                EnableDynamicInstall = true,
                EnableExternalAcrSdkProvider = true,
                EnableExternalSdkProvider = true,
                EnableAcrSdkProvider = true,
                DebianFlavor = OsTypes.DebianBookworm,
            };
            var phpPlatform = CreatePhpPlatformWithProviders(
                commonOptions: commonOptions,
                isPhpVersionAlreadyInstalled: false,
                isPhpComposerAlreadyInstalled: false,
                externalAcrSdkProvider: externalAcrSdkProvider,
                acrSdkProvider: acrSdkProvider,
                externalSdkProvider: externalSdkProvider);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert
            Assert.NotNull(actualScriptSnippet);
            Assert.Contains(TestPhpPlatformInstaller.InstallerScript, actualScriptSnippet);
            Assert.Contains(TestPhpComposerInstaller.InstallerScript, actualScriptSnippet);
        }

        [Fact]
        public void ComposerFallsBackFromExternalAcrToExternalSdk_WhenExternalAcrFails()
        {
            // Arrange
            var externalAcrSdkProvider = new TestExternalAcrSdkProvider(returnValue: false);
            var commonOptions = new BuildScriptGeneratorOptions()
            {
                EnableDynamicInstall = true,
                EnableExternalAcrSdkProvider = true,
                EnableExternalSdkProvider = true,
                DebianFlavor = OsTypes.DebianBookworm,
            };
            var phpPlatform = CreatePhpPlatformWithProviders(
                commonOptions: commonOptions,
                isPhpVersionAlreadyInstalled: false,
                isPhpComposerAlreadyInstalled: false,
                externalAcrSdkProvider: externalAcrSdkProvider);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert - Both PHP and Composer should fall from ExternalACR to ExternalSDK
            Assert.NotNull(actualScriptSnippet);
            Assert.Contains(TestPhpPlatformInstaller.InstallerScriptWithSkipSdkBinaryDownload, actualScriptSnippet);
            Assert.Contains(TestPhpComposerInstaller.InstallerScriptWithSkipSdkBinaryDownload, actualScriptSnippet);
            Assert.True(externalAcrSdkProvider.RequestSdkAsyncCalled);
        }

        [Fact]
        public void ComposerFallsBackFromExternalAcrToDirectAcr_WhenBothExternalProvidersFail()
        {
            // Arrange
            var externalAcrSdkProvider = new TestExternalAcrSdkProvider(returnValue: false);
            var acrSdkProvider = new TestAcrSdkProvider(returnValue: true);
            var externalSdkProvider = new TestExternalSdkProvider(requestBlobResult: false);
            var commonOptions = new BuildScriptGeneratorOptions()
            {
                EnableDynamicInstall = true,
                EnableExternalAcrSdkProvider = true,
                EnableExternalSdkProvider = true,
                EnableAcrSdkProvider = true,
                DebianFlavor = OsTypes.DebianBookworm,
            };
            var phpPlatform = CreatePhpPlatformWithProviders(
                commonOptions: commonOptions,
                isPhpVersionAlreadyInstalled: false,
                isPhpComposerAlreadyInstalled: false,
                externalAcrSdkProvider: externalAcrSdkProvider,
                acrSdkProvider: acrSdkProvider,
                externalSdkProvider: externalSdkProvider);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "7.3.5",
            };

            // Act
            var actualScriptSnippet = phpPlatform.GetInstallerScriptSnippet(context, detectedResult);

            // Assert - Both PHP and Composer should fall from ExternalACR+ExternalSDK to DirectACR
            Assert.NotNull(actualScriptSnippet);
            Assert.Contains(TestPhpPlatformInstaller.InstallerScriptWithSkipSdkBinaryDownload, actualScriptSnippet);
            Assert.Contains(TestPhpComposerInstaller.InstallerScriptWithSkipSdkBinaryDownload, actualScriptSnippet);
            Assert.True(externalAcrSdkProvider.RequestSdkAsyncCalled);
            Assert.True(acrSdkProvider.RequestSdkFromAcrAsyncCalled);
        }

        [Fact]
        public void Detect_ReturnsDetectedVersion_WhenExternalAcrEnabled_ButVersionNotFromExternalAcr()
        {
            // Arrange
            var detectedVersion = "8.3.0";
            var versionProviderDefault = "8.4.0";
            var repo = new MemorySourceRepo();
            repo.AddFile("<?php echo true; ?>", "foo.php");
            var context = CreateContext(repo);
            var commonOptions = new BuildScriptGeneratorOptions
            {
                EnableExternalAcrSdkProvider = true,
            };
            var platform = CreatePhpPlatform(
                supportedPhpVersions: new[] { detectedVersion, versionProviderDefault },
                defaultVersion: versionProviderDefault,
                detectedVersion: detectedVersion,
                commonOptions: commonOptions);

            // Act
            var result = platform.Detect(context);

            // Assert - When the version info did not come from the External ACR provider,
            // the detected version should be used instead of the provider's default
            Assert.NotNull(result);
            Assert.Equal(PhpConstants.PlatformName, result.Platform);
            Assert.Equal(detectedVersion, result.PlatformVersion);
        }

        [Fact]
        public void Detect_ReturnsUserSpecifiedVersion_WhenExternalAcrEnabled_ButVersionNotFromExternalAcr()
        {
            // Arrange
            var userVersion = "8.3.0";
            var detectedVersion = "8.2.0";
            var versionProviderDefault = "8.4.0";
            var repo = new MemorySourceRepo();
            repo.AddFile("<?php echo true; ?>", "foo.php");
            var context = CreateContext(repo);
            var commonOptions = new BuildScriptGeneratorOptions
            {
                EnableExternalAcrSdkProvider = true,
            };
            var phpScriptGeneratorOptions = new PhpScriptGeneratorOptions
            {
                PhpVersion = userVersion,
            };
            var platform = CreatePhpPlatform(
                supportedPhpVersions: new[] { userVersion, detectedVersion, versionProviderDefault },
                defaultVersion: versionProviderDefault,
                detectedVersion: detectedVersion,
                commonOptions: commonOptions,
                phpScriptGeneratorOptions: phpScriptGeneratorOptions);

            // Act
            var result = platform.Detect(context);

            // Assert - When ExternalACR fails and blob fallback is used,
            // --platform-version should win over the blob default
            Assert.NotNull(result);
            Assert.Equal(PhpConstants.PlatformName, result.Platform);
            Assert.Equal(userVersion, result.PlatformVersion);
        }

        [Fact]
        public void Detect_ReturnsExternalAcrVersion_WhenVersionSourceIsExternalAcr()
        {
            // Arrange
            var detectedVersion = "8.3.0";
            var externalAcrVersion = "8.4.0";
            var repo = new MemorySourceRepo();
            repo.AddFile("<?php echo true; ?>", "foo.php");
            var context = CreateContext(repo);
            var commonOptions = new BuildScriptGeneratorOptions
            {
                EnableExternalAcrSdkProvider = true,
            };
            var versionProvider = new TestPhpVersionProvider(
                new[] { detectedVersion, externalAcrVersion },
                externalAcrVersion,
                PlatformVersionSourceType.AvailableViaExternalAcrProvider);
            var composerVersionProvider = new TestPhpComposerVersionProvider(
                new[] { PhpVersions.ComposerDefaultVersion },
                PhpVersions.ComposerDefaultVersion);
            var detector = new TestPhpPlatformDetector(detectedVersion: detectedVersion);
            var phpInstaller = new TestPhpPlatformInstaller(
                Options.Create(commonOptions),
                isVersionAlreadyInstalled: true);
            var phpComposerInstaller = new TestPhpComposerInstaller(
                Options.Create(commonOptions),
                isVersionAlreadyInstalled: true);
            var platform = new TestPhpPlatform(
                Options.Create(new PhpScriptGeneratorOptions()),
                Options.Create(commonOptions),
                versionProvider,
                composerVersionProvider,
                NullLogger<TestPhpPlatform>.Instance,
                detector,
                phpInstaller,
                phpComposerInstaller,
                new TestExternalSdkProvider(),
                new TestExternalAcrSdkProvider(),
                new TestAcrSdkProvider(),
                TelemetryClientHelper.GetTelemetryClient(),
                new DefaultStandardOutputWriter());

            // Act
            var result = platform.Detect(context);

            // Assert - When version truly came from External ACR, it should override detected version
            Assert.NotNull(result);
            Assert.Equal(PhpConstants.PlatformName, result.Platform);
            Assert.Equal(externalAcrVersion, result.PlatformVersion);
        }

        [Fact]
        public void Detect_UserSpecifiedComposerVersion_WinsOverExternalAcrProviderDefault()
        {
            // Arrange
            var userSpecifiedComposerVersion = "2.5.8";
            var acrProviderDefaultComposerVersion = "2.0.0";
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            repo.AddFile("<?php echo true; ?>", "foo.php");
            var context = CreateContext(repo);
            var commonOptions = new BuildScriptGeneratorOptions
            {
                EnableExternalAcrSdkProvider = true,
            };
            var phpScriptGeneratorOptions = new PhpScriptGeneratorOptions
            {
                PhpComposerVersion = userSpecifiedComposerVersion,
            };
            var platform = CreatePhpPlatform(
                supportedPhpVersions: new[] { "7.3.5" },
                defaultVersion: "7.3.5",
                detectedVersion: "7.3.5",
                supportedPhpComposerVersions: new[] { userSpecifiedComposerVersion, acrProviderDefaultComposerVersion },
                defaultComposerVersion: acrProviderDefaultComposerVersion,
                commonOptions: commonOptions,
                phpScriptGeneratorOptions: phpScriptGeneratorOptions);

            // Act
            var result = platform.Detect(context) as PhpPlatformDetectorResult;

            // Assert - user-specified PHP_COMPOSER_VERSION must win over the ACR provider default
            Assert.NotNull(result);
            Assert.Equal(userSpecifiedComposerVersion, result.PhpComposerVersion);
        }

        [Theory]
        [InlineData(null, "7.4.30", null, "7.4.30")]
        [InlineData(null, "7.4.30", "7.3.1", "7.4.30")]
        [InlineData(null, null, "7.3.1", "7.3.1")]
        [InlineData("8.0.6", "7.4.30", "7.3.1", "8.0.6")]
        public void Detect_ReturnsExpectedVersion_BasedOnHierarchy(
            string detectedVersion,
            string envVarDefaultVersion,
            string detectedDefaultVersion,
            string expectedSdkVersion)
        {
            // Arrange
            var repo = new MemorySourceRepo();
            repo.AddFile("<?php echo true; ?>", "foo.php");
            var context = CreateContext(repo);
            var options = new PhpScriptGeneratorOptions();
            options.PhpDefaultVersion = envVarDefaultVersion;
            var platform = CreatePhpPlatform(
                detectedVersion: detectedVersion,
                defaultVersion: detectedDefaultVersion,
                phpScriptGeneratorOptions: options,
                supportedPhpVersions: new[] { detectedVersion, detectedDefaultVersion, envVarDefaultVersion }.Where(x => !string.IsNullOrEmpty(x)).ToArray());

            // Act
            var result = platform.Detect(context);

            // Assert
            Assert.NotNull(result);
            Assert.Equal(PhpConstants.PlatformName, result.Platform);
            Assert.Equal(expectedSdkVersion, result.PlatformVersion);
        }

        [Fact]
        public void GeneratedBuildSnippet_CustomBuildCommandWillExecute_InsteadOfComposerInstall()
        {
            // Arrange
            var phpScriptGeneratorOptions = new PhpScriptGeneratorOptions
            {
                CustomBuildCommand = "make build",
            };
            var phpPlatform = CreatePhpPlatform(
                phpScriptGeneratorOptions: phpScriptGeneratorOptions);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "8.4.0",
            };

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

            // Assert
            Assert.NotNull(buildScriptSnippet);
            Assert.Contains("make build", buildScriptSnippet.BashBuildScriptSnippet);
            Assert.DoesNotContain("composer install", buildScriptSnippet.BashBuildScriptSnippet);
        }

        [Fact]
        public void GeneratedBuildSnippet_CustomBuildCommandWillExecute_EvenWithoutComposerJson()
        {
            // Arrange
            var phpScriptGeneratorOptions = new PhpScriptGeneratorOptions
            {
                CustomBuildCommand = "php artisan build",
            };
            var phpPlatform = CreatePhpPlatform(
                phpScriptGeneratorOptions: phpScriptGeneratorOptions);
            var repo = new MemorySourceRepo();
            repo.AddFile("<?php echo 'hi'; ?>", "index.php");
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "8.4.0",
            };

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

            // Assert
            Assert.NotNull(buildScriptSnippet);
            Assert.Contains("php artisan build", buildScriptSnippet.BashBuildScriptSnippet);
            Assert.DoesNotContain("No 'composer.json' file found", buildScriptSnippet.BashBuildScriptSnippet);
        }

        [Fact]
        public void GeneratedBuildSnippet_UsesComposerInstall_WhenNoCustomBuildCommandSet()
        {
            // Arrange
            var phpPlatform = CreatePhpPlatform();
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "8.4.0",
            };

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

            // Assert
            Assert.NotNull(buildScriptSnippet);
            Assert.Contains("composer install", buildScriptSnippet.BashBuildScriptSnippet);
            Assert.DoesNotContain("Running custom build command", buildScriptSnippet.BashBuildScriptSnippet);
        }

        [Fact]
        public void GeneratedBuildSnippet_PhpExecutableDisplayIsPreserved_WithCustomBuildCommand()
        {
            // Arrange
            var phpScriptGeneratorOptions = new PhpScriptGeneratorOptions
            {
                CustomBuildCommand = "make build",
            };
            var phpPlatform = CreatePhpPlatform(
                phpScriptGeneratorOptions: phpScriptGeneratorOptions);
            var repo = new MemorySourceRepo();
            repo.AddFile("{}", PhpConstants.ComposerFileName);
            var context = CreateContext(repo);
            var detectedResult = new PhpPlatformDetectorResult
            {
                Platform = PhpConstants.PlatformName,
                PlatformVersion = "8.4.0",
            };

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

            // Assert
            Assert.NotNull(buildScriptSnippet);
            Assert.Contains("which php", buildScriptSnippet.BashBuildScriptSnippet);
            Assert.Contains("PHP executable", buildScriptSnippet.BashBuildScriptSnippet);
            Assert.Contains("make build", buildScriptSnippet.BashBuildScriptSnippet);
        }

        private PhpPlatform CreatePhpPlatform(
            string[] supportedPhpVersions = null,
            string[] supportedPhpComposerVersions = null,
            string defaultVersion = null,
            string defaultComposerVersion = null,
            string detectedVersion = null,
            BuildScriptGeneratorOptions commonOptions = null,
            PhpScriptGeneratorOptions phpScriptGeneratorOptions = null,
            bool? isPhpVersionAlreadyInstalled = null,
            bool? isPhpComposerAlreadyInstalled = null)
        {
            commonOptions = commonOptions ?? new BuildScriptGeneratorOptions();
            phpScriptGeneratorOptions = phpScriptGeneratorOptions ?? new PhpScriptGeneratorOptions();
            isPhpVersionAlreadyInstalled = isPhpVersionAlreadyInstalled ?? true;
            isPhpComposerAlreadyInstalled = isPhpComposerAlreadyInstalled ?? true;
            var versionProvider = new TestPhpVersionProvider(supportedPhpVersions, defaultVersion);
            var externalSdkProvider = new TestExternalSdkProvider();
            supportedPhpComposerVersions = supportedPhpComposerVersions ?? new[] { PhpVersions.ComposerDefaultVersion };
            defaultComposerVersion = defaultComposerVersion ?? PhpVersions.ComposerDefaultVersion;
            var composerVersionProvider = new TestPhpComposerVersionProvider(
                supportedPhpComposerVersions,
                defaultComposerVersion);
            var detector = new TestPhpPlatformDetector(detectedVersion: detectedVersion);
            var phpInstaller = new TestPhpPlatformInstaller(
                Options.Create(commonOptions),
                isPhpVersionAlreadyInstalled.Value);
            var phpComposerInstaller = new TestPhpComposerInstaller(
                Options.Create(commonOptions),
                isPhpComposerAlreadyInstalled.Value);   
            return new TestPhpPlatform(
                Options.Create(phpScriptGeneratorOptions),
                Options.Create(commonOptions),
                versionProvider,
                composerVersionProvider,
                NullLogger<TestPhpPlatform>.Instance,
                detector,
                phpInstaller,
                phpComposerInstaller,
                externalSdkProvider,
                new TestExternalAcrSdkProvider(),
                new TestAcrSdkProvider(),
                TelemetryClientHelper.GetTelemetryClient(),
                new DefaultStandardOutputWriter());
        }

        private PhpPlatform CreatePhpPlatformWithProviders(
            BuildScriptGeneratorOptions commonOptions = null,
            bool? isPhpVersionAlreadyInstalled = null,
            bool? isPhpComposerAlreadyInstalled = null,
            IExternalAcrSdkProvider externalAcrSdkProvider = null,
            IAcrSdkProvider acrSdkProvider = null,
            IExternalSdkProvider externalSdkProvider = null)
        {
            commonOptions = commonOptions ?? new BuildScriptGeneratorOptions();
            var phpScriptGeneratorOptions = new PhpScriptGeneratorOptions();
            isPhpVersionAlreadyInstalled = isPhpVersionAlreadyInstalled ?? true;
            isPhpComposerAlreadyInstalled = isPhpComposerAlreadyInstalled ?? true;
            var versionProvider = new TestPhpVersionProvider(null, null);
            externalSdkProvider = externalSdkProvider ?? new TestExternalSdkProvider();
            externalAcrSdkProvider = externalAcrSdkProvider ?? new TestExternalAcrSdkProvider();
            acrSdkProvider = acrSdkProvider ?? new TestAcrSdkProvider();
            var composerVersionProvider = new TestPhpComposerVersionProvider(
                new[] { PhpVersions.ComposerDefaultVersion },
                PhpVersions.ComposerDefaultVersion);
            var detector = new TestPhpPlatformDetector(detectedVersion: null);
            var phpInstaller = new TestPhpPlatformInstaller(
                Options.Create(commonOptions),
                isPhpVersionAlreadyInstalled.Value);
            var phpComposerInstaller = new TestPhpComposerInstaller(
                Options.Create(commonOptions),
                isPhpComposerAlreadyInstalled.Value);
            return new TestPhpPlatform(
                Options.Create(phpScriptGeneratorOptions),
                Options.Create(commonOptions),
                versionProvider,
                composerVersionProvider,
                NullLogger<TestPhpPlatform>.Instance,
                detector,
                phpInstaller,
                phpComposerInstaller,
                externalSdkProvider,
                externalAcrSdkProvider,
                acrSdkProvider,
                TelemetryClientHelper.GetTelemetryClient(),
                new DefaultStandardOutputWriter());
        }

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

            return new BuildScriptGeneratorContext
            {
                SourceRepo = sourceRepo,
            };
        }

        private class TestPhpPlatform : PhpPlatform
        {
            public TestPhpPlatform(
                IOptions<PhpScriptGeneratorOptions> phpScriptGeneratorOptions,
                IOptions<BuildScriptGeneratorOptions> commonOptions,
                IPhpVersionProvider phpVersionProvider,
                IPhpComposerVersionProvider phpComposerVersionProvider,
                ILogger<PhpPlatform> logger,
                IPhpPlatformDetector detector,
                PhpPlatformInstaller phpInstaller,
                PhpComposerInstaller phpComposerInstaller,
                IExternalSdkProvider externalSdkProvider,
                IExternalAcrSdkProvider externalAcrSdkProvider,
                IAcrSdkProvider acrSdkProvider,
                TelemetryClient telemetryClient,
                IStandardOutputWriter outputWriter)
                : base(
                      phpScriptGeneratorOptions,
                      commonOptions,
                      phpVersionProvider,
                      phpComposerVersionProvider,
                      logger,
                      detector,
                      phpInstaller,
                      phpComposerInstaller,
                      externalSdkProvider,
                      externalAcrSdkProvider,
                      acrSdkProvider,
                      telemetryClient,
                      outputWriter)
            {
            }
        }

        private class TestPhpPlatformInstaller : PhpPlatformInstaller
        {
            public static string InstallerScript = "default-php-installation-script";
            public static string InstallerScriptWithSkipSdkBinaryDownload = "default-php-installation-script-with-skip-sdk-binary-download";
            private readonly bool _isVersionAlreadyInstalled;

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

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

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

        private class TestPhpComposerInstaller : PhpComposerInstaller
        {
            public static string InstallerScript = "default-php-composer-installation-script";
            public static string InstallerScriptWithSkipSdkBinaryDownload = "default-php-composer-installation-script-with-skip-sdk-binary-download";
            private readonly bool _isVersionAlreadyInstalled;

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

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

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

        private class TestPhpComposerVersionProvider : IPhpComposerVersionProvider
        {
            private readonly string[] _supportedPhpComposerVersions;
            private readonly string _defaultVersion;

            public TestPhpComposerVersionProvider(string[] supportedPhpComposerVersions, string defaultVersion)
            {
                _supportedPhpComposerVersions = supportedPhpComposerVersions;
                _defaultVersion = defaultVersion;
            }

            public PlatformVersionInfo GetVersionInfo()
            {
                return PlatformVersionInfo.CreateOnDiskVersionInfo(
                    _supportedPhpComposerVersions,
                    _defaultVersion);
            }
        }
    }
}
