// 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.IO;
using System.Linq;
using System.Security.Principal;
using System.Xml.Linq;
using Microsoft.AspNetCore.Testing;
using Microsoft.Win32;

namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests;

[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
public sealed class RequiresIISAttribute : Attribute, ITestCondition
{
    private static readonly (IISCapability Capability, string DllName)[] Modules =
    {
            (IISCapability.Websockets, "iiswsock.dll"),
            (IISCapability.WindowsAuthentication, "authsspi.dll"),
            (IISCapability.DynamicCompression, "compdyn.dll"),
            (IISCapability.ApplicationInitialization, "warmup.dll"),
            (IISCapability.TracingModule, "iisetw.dll"),
            (IISCapability.FailedRequestTracingModule, "iisfreb.dll"),
            (IISCapability.BasicAuthentication, "authbas.dll"),
        };

    private static readonly bool _isMetStatic;
    private static readonly string _skipReasonStatic;
    private static readonly bool _poolEnvironmentVariablesAvailable;
    private static readonly IISCapability _modulesAvailable;

    static RequiresIISAttribute()
    {
        if (Environment.GetEnvironmentVariable("ASPNETCORE_TEST_SKIP_IIS") == "true")
        {
            _skipReasonStatic = "Test skipped using ASPNETCORE_TEST_SKIP_IIS environment variable";
            return;
        }

        if (!OperatingSystem.IsWindows())
        {
            _skipReasonStatic = "IIS tests can only be run on Windows";
            return;
        }

        var identity = WindowsIdentity.GetCurrent();
        var principal = new WindowsPrincipal(identity);
        if (!principal.IsInRole(WindowsBuiltInRole.Administrator) && !SkipInVSTSAttribute.RunningInVSTS)
        {
            _skipReasonStatic += "The current console is not running as admin.";
            return;
        }

        if (!File.Exists(Path.Combine(Environment.SystemDirectory, "inetsrv", "w3wp.exe")) && !SkipInVSTSAttribute.RunningInVSTS)
        {
            _skipReasonStatic += "The machine does not have IIS installed.";
            return;
        }

        var ancmConfigPath = Path.Combine(Environment.SystemDirectory, "inetsrv", "config", "schema", "aspnetcore_schema.xml");

        if (!File.Exists(ancmConfigPath))
        {
            ancmConfigPath = Path.Combine(Environment.SystemDirectory, "inetsrv", "config", "schema", "aspnetcore_schema_v2.xml");
        }

        if (!File.Exists(ancmConfigPath) && !SkipInVSTSAttribute.RunningInVSTS)
        {
            _skipReasonStatic = "IIS Schema is not installed.";
            return;
        }

        XDocument ancmConfig;

        try
        {
            ancmConfig = XDocument.Load(ancmConfigPath);
        }
        catch
        {
            _skipReasonStatic = "Could not read ANCM schema configuration";
            return;
        }

        _isMetStatic = ancmConfig
            .Root
            .Descendants("attribute")
            .Any(n => "hostingModel".Equals(n.Attribute("name")?.Value, StringComparison.Ordinal));

        _skipReasonStatic = _isMetStatic ? null : "IIS schema needs to be upgraded to support ANCM.";

        foreach (var module in Modules)
        {
            if (File.Exists(Path.Combine(Environment.SystemDirectory, "inetsrv", module.DllName)) || SkipInVSTSAttribute.RunningInVSTS)
            {
                _modulesAvailable |= module.Capability;
            }
        }

        var iisRegistryKey = Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\InetStp", writable: false);
        if (iisRegistryKey == null)
        {
            _poolEnvironmentVariablesAvailable = false;
        }
        else
        {
            var majorVersion = (int)iisRegistryKey.GetValue("MajorVersion", -1);
            var minorVersion = (int)iisRegistryKey.GetValue("MinorVersion", -1);
            var version = new Version(majorVersion, minorVersion);
            _poolEnvironmentVariablesAvailable = version >= new Version(10, 0);
        }
    }

    public RequiresIISAttribute()
        : this(IISCapability.None) { }

    public RequiresIISAttribute(IISCapability capabilities)
    {
        IsMet = _isMetStatic;
        SkipReason = _skipReasonStatic;
        if (capabilities.HasFlag(IISCapability.PoolEnvironmentVariables))
        {
            IsMet &= _poolEnvironmentVariablesAvailable;
            if (!_poolEnvironmentVariablesAvailable)
            {
                SkipReason += "The machine does allow for setting environment variables on application pools.";
            }
        }

        foreach (var module in Modules)
        {
            if (capabilities.HasFlag(module.Capability))
            {
                var available = _modulesAvailable.HasFlag(module.Capability);
                IsMet &= available;
                if (!available)
                {
                    SkipReason += $"The machine does have {module.Capability} available.";
                }
            }
        }
    }

    public bool IsMet { get; }
    public string SkipReason { get; }
}
