using GitHub.Runner.Common.Util;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Xunit;
using System;
using GitHub.Runner.Sdk;

namespace GitHub.Runner.Common.Tests
{
    public sealed class RunnerWebProxyL0
    {
        private static readonly Regex NewHttpClientHandlerRegex = new("New\\s+HttpClientHandler\\s*\\(", RegexOptions.Compiled | RegexOptions.IgnoreCase);
        private static readonly Regex NewHttpClientRegex = new("New\\s+HttpClient\\s*\\(\\s*\\)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
        private static readonly List<string> SkippedFiles = new()
        {
            "Runner.Common\\HostContext.cs",
            "Runner.Common/HostContext.cs",
            "Runner.Common\\HttpClientHandlerFactory.cs",
            "Runner.Common/HttpClientHandlerFactory.cs"
        };

        [Fact]
        [Trait("Level", "L0")]
        [Trait("Category", "Common")]
        public void IsNotUseRawHttpClientHandler()
        {
            List<string> sourceFiles = Directory.GetFiles(
                    TestUtil.GetProjectPath("Runner.Common"),
                    "*.cs",
                    SearchOption.AllDirectories).ToList();
            sourceFiles.AddRange(Directory.GetFiles(
                     TestUtil.GetProjectPath("Runner.Listener"),
                     "*.cs",
                     SearchOption.AllDirectories));
            sourceFiles.AddRange(Directory.GetFiles(
                    TestUtil.GetProjectPath("Runner.Worker"),
                    "*.cs",
                    SearchOption.AllDirectories));

            List<string> badCode = new();
            foreach (string sourceFile in sourceFiles)
            {
                // Skip skipped files.
                if (SkippedFiles.Any(s => sourceFile.Contains(s)))
                {
                    continue;
                }

                // Skip files in the obj directory.
                if (sourceFile.Contains(StringUtil.Format("{0}obj{0}", Path.DirectorySeparatorChar)))
                {
                    continue;
                }

                int lineCount = 0;
                foreach (string line in File.ReadAllLines(sourceFile))
                {
                    lineCount++;
                    if (NewHttpClientHandlerRegex.IsMatch(line))
                    {
                        badCode.Add($"{sourceFile} (line {lineCount})");
                    }
                }
            }

            if (badCode.Count > 0)
            {
                Assert.Fail($"The following code is using Raw HttpClientHandler() which will not follow the proxy setting agent have. Please use HostContext.CreateHttpClientHandler() instead.\n {string.Join("\n", badCode)}");
            }
            else
            {
                Assert.True(true);
            }
        }

        [Fact]
        [Trait("Level", "L0")]
        [Trait("Category", "Common")]
        public void IsNotUseRawHttpClient()
        {
            List<string> sourceFiles = Directory.GetFiles(
                    TestUtil.GetProjectPath("Runner.Common"),
                    "*.cs",
                    SearchOption.AllDirectories).ToList();
            sourceFiles.AddRange(Directory.GetFiles(
                     TestUtil.GetProjectPath("Runner.Listener"),
                     "*.cs",
                     SearchOption.AllDirectories));
            sourceFiles.AddRange(Directory.GetFiles(
                    TestUtil.GetProjectPath("Runner.Worker"),
                    "*.cs",
                    SearchOption.AllDirectories));

            List<string> badCode = new();
            foreach (string sourceFile in sourceFiles)
            {
                // Skip skipped files.
                if (SkippedFiles.Any(s => sourceFile.Contains(s)))
                {
                    continue;
                }

                // Skip files in the obj directory.
                if (sourceFile.Contains(StringUtil.Format("{0}obj{0}", Path.DirectorySeparatorChar)))
                {
                    continue;
                }

                int lineCount = 0;
                foreach (string line in File.ReadAllLines(sourceFile))
                {
                    lineCount++;
                    if (NewHttpClientRegex.IsMatch(line))
                    {
                        badCode.Add($"{sourceFile} (line {lineCount})");
                    }
                }
            }

            if (badCode.Count > 0)
            {
                Assert.Fail($"The following code is using Raw HttpClient() which will not follow the proxy setting agent have. Please use New HttpClient(HostContext.CreateHttpClientHandler()) instead.\n {string.Join("\n", badCode)}");
            }
            else
            {
                Assert.True(true);
            }
        }

        [Fact]
        [Trait("Level", "L0")]
        [Trait("Category", "Common")]
        public void WebProxyFromEnvironmentVariables()
        {
            try
            {
                Environment.SetEnvironmentVariable("http_proxy", "http://127.0.0.1:8888");
                Environment.SetEnvironmentVariable("https_proxy", "http://user:pass@127.0.0.1:9999");
                Environment.SetEnvironmentVariable("no_proxy", "github.com, google.com,");
                var proxy = new RunnerWebProxy();

                Assert.Equal("http://127.0.0.1:8888", proxy.HttpProxyAddress);
                Assert.Null(proxy.HttpProxyUsername);
                Assert.Null(proxy.HttpProxyPassword);

                Assert.Equal("http://user:pass@127.0.0.1:9999", proxy.HttpsProxyAddress);
                Assert.Equal("user", proxy.HttpsProxyUsername);
                Assert.Equal("pass", proxy.HttpsProxyPassword);

                Assert.Equal(2, proxy.NoProxyList.Count);
                Assert.Equal("github.com", proxy.NoProxyList[0].Host);
                Assert.Equal("google.com", proxy.NoProxyList[1].Host);
            }
            finally
            {
                CleanProxyEnv();
            }
        }

#if !OS_WINDOWS
        [Fact]
        [Trait("Level", "L0")]
        [Trait("Category", "Common")]
        public void WebProxyFromEnvironmentVariablesPreferLowerCase()
        {
            try
            {
                Environment.SetEnvironmentVariable("http_proxy", "http://127.0.0.1:7777");
                Environment.SetEnvironmentVariable("HTTP_PROXY", "http://127.0.0.1:8888");
                Environment.SetEnvironmentVariable("https_proxy", "http://user:pass@127.0.0.1:8888");
                Environment.SetEnvironmentVariable("HTTPS_PROXY", "http://user:pass@127.0.0.1:9999");
                Environment.SetEnvironmentVariable("no_proxy", "github.com,  github.com  ");
                Environment.SetEnvironmentVariable("NO_PROXY", "github.com, google.com,");
                var proxy = new RunnerWebProxy();

                Assert.Equal("http://127.0.0.1:7777", proxy.HttpProxyAddress);
                Assert.Null(proxy.HttpProxyUsername);
                Assert.Null(proxy.HttpProxyPassword);

                Assert.Equal("http://user:pass@127.0.0.1:8888", proxy.HttpsProxyAddress);
                Assert.Equal("user", proxy.HttpsProxyUsername);
                Assert.Equal("pass", proxy.HttpsProxyPassword);

                Assert.Equal(1, proxy.NoProxyList.Count);
                Assert.Equal("github.com", proxy.NoProxyList[0].Host);
            }
            finally
            {
                CleanProxyEnv();
            }
        }
#endif

        [Fact]
        [Trait("Level", "L0")]
        [Trait("Category", "Common")]
        public void WebProxyFromEnvironmentVariablesInvalidString()
        {
            try
            {
                Environment.SetEnvironmentVariable("http_proxy", "#fragment");
                Environment.SetEnvironmentVariable("https_proxy", "#fragment");
                var proxy = new RunnerWebProxy();

                Assert.Null(proxy.HttpProxyAddress);
                Assert.Null(proxy.HttpProxyUsername);
                Assert.Null(proxy.HttpProxyPassword);

                Assert.Null(proxy.HttpsProxyAddress);
                Assert.Null(proxy.HttpsProxyUsername);
                Assert.Null(proxy.HttpsProxyPassword);

                Assert.Equal(0, proxy.NoProxyList.Count);
            }
            finally
            {
                CleanProxyEnv();
            }
        }

        [Fact]
        [Trait("Level", "L0")]
        [Trait("Category", "Common")]
        public void WebProxyPrependsHTTPforHTTP_PROXY_IfNoProtocol()
        {
            try
            {
                Environment.SetEnvironmentVariable("http_proxy", "127.0.0.1:7777");
                var proxy = new RunnerWebProxy();

                Assert.Equal("http://127.0.0.1:7777", proxy.HttpProxyAddress);
                Assert.Null(proxy.HttpProxyUsername);
                Assert.Null(proxy.HttpProxyPassword);

                Assert.Equal(0, proxy.NoProxyList.Count);

                Environment.SetEnvironmentVariable("http_proxy", "http://127.0.0.1:7777");
                proxy = new RunnerWebProxy();

                Assert.Equal("http://127.0.0.1:7777", proxy.HttpProxyAddress);
                Assert.Null(proxy.HttpProxyUsername);
                Assert.Null(proxy.HttpProxyPassword);

                Assert.Equal(0, proxy.NoProxyList.Count);
            }
            finally
            {
                CleanProxyEnv();
            }
        }

        [Fact]
        [Trait("Level", "L0")]
        [Trait("Category", "Common")]
        public void WebProxyPrependsHTTPforHTTPS_PROXY_IfNoProtocol()
        {
            try
            {
                Environment.SetEnvironmentVariable("https_proxy", "127.0.0.1:7777");
                var proxy = new RunnerWebProxy();

                Assert.Equal("http://127.0.0.1:7777", proxy.HttpsProxyAddress);
                Assert.Null(proxy.HttpProxyUsername);
                Assert.Null(proxy.HttpProxyPassword);

                Assert.Equal(0, proxy.NoProxyList.Count);

                Environment.SetEnvironmentVariable("https_proxy", "https://127.0.0.1:7777");
                proxy = new RunnerWebProxy();

                Assert.Equal("https://127.0.0.1:7777", proxy.HttpsProxyAddress); // existing protocol 'https' is not removed
                Assert.Null(proxy.HttpProxyUsername);
                Assert.Null(proxy.HttpProxyPassword);

                Assert.Equal(0, proxy.NoProxyList.Count);
            }
            finally
            {
                CleanProxyEnv();
            }
        }

        [Fact]
        [Trait("Level", "L0")]
        [Trait("Category", "Common")]
        public void WebProxyFromEnvironmentVariablesProxyCredentials()
        {
            try
            {
                Environment.SetEnvironmentVariable("http_proxy", "http://user1@127.0.0.1:8888");
                Environment.SetEnvironmentVariable("https_proxy", "http://user2:pass@127.0.0.1:9999");
                Environment.SetEnvironmentVariable("no_proxy", "github.com, google.com,");
                var proxy = new RunnerWebProxy();

                Assert.Equal("http://user1@127.0.0.1:8888", proxy.HttpProxyAddress);
                Assert.Equal("user1", proxy.HttpProxyUsername);
                Assert.Null(proxy.HttpProxyPassword);

                var cred = proxy.Credentials.GetCredential(new Uri("http://user1@127.0.0.1:8888"), "Basic");
                Assert.Equal("user1", cred.UserName);
                Assert.Equal(string.Empty, cred.Password);

                Assert.Equal("http://user2:pass@127.0.0.1:9999", proxy.HttpsProxyAddress);
                Assert.Equal("user2", proxy.HttpsProxyUsername);
                Assert.Equal("pass", proxy.HttpsProxyPassword);

                cred = proxy.Credentials.GetCredential(new Uri("http://user2:pass@127.0.0.1:9999"), "Basic");
                Assert.Equal("user2", cred.UserName);
                Assert.Equal("pass", cred.Password);

                Assert.Equal(2, proxy.NoProxyList.Count);
                Assert.Equal("github.com", proxy.NoProxyList[0].Host);
                Assert.Equal("google.com", proxy.NoProxyList[1].Host);
            }
            finally
            {
                CleanProxyEnv();
            }
        }

        [Fact]
        [Trait("Level", "L0")]
        [Trait("Category", "Common")]
        public void WebProxyFromEnvironmentVariablesProxyCredentialsEncoding()
        {
            try
            {
                Environment.SetEnvironmentVariable("http_proxy", "http://user1:pass1%40@127.0.0.1:8888");
                Environment.SetEnvironmentVariable("https_proxy", "http://user2:pass2%40@127.0.0.1:9999");
                Environment.SetEnvironmentVariable("no_proxy", "github.com, google.com,");
                var proxy = new RunnerWebProxy();

                Assert.Equal("http://user1:pass1%40@127.0.0.1:8888", proxy.HttpProxyAddress);
                Assert.Equal("user1", proxy.HttpProxyUsername);
                Assert.Equal("pass1@", proxy.HttpProxyPassword);

                var cred = proxy.Credentials.GetCredential(new Uri("http://user1:pass1%40@127.0.0.1:8888"), "Basic");
                Assert.Equal("user1", cred.UserName);
                Assert.Equal("pass1@", cred.Password);

                Assert.Equal("http://user2:pass2%40@127.0.0.1:9999", proxy.HttpsProxyAddress);
                Assert.Equal("user2", proxy.HttpsProxyUsername);
                Assert.Equal("pass2@", proxy.HttpsProxyPassword);

                cred = proxy.Credentials.GetCredential(new Uri("http://user2:pass2%40@127.0.0.1:9999"), "Basic");
                Assert.Equal("user2", cred.UserName);
                Assert.Equal("pass2@", cred.Password);

                Assert.Equal(2, proxy.NoProxyList.Count);
                Assert.Equal("github.com", proxy.NoProxyList[0].Host);
                Assert.Equal("google.com", proxy.NoProxyList[1].Host);
            }
            finally
            {
                CleanProxyEnv();
            }
        }

        [Fact]
        [Trait("Level", "L0")]
        [Trait("Category", "Common")]
        public void WebProxyFromEnvironmentVariablesByPassEmptyProxy()
        {
            var proxy = new RunnerWebProxy();
            Assert.True(proxy.IsBypassed(new Uri("https://github.com")));
            Assert.True(proxy.IsBypassed(new Uri("https://github.com")));
        }

        [Fact]
        [Trait("Level", "L0")]
        [Trait("Category", "Common")]
        public void WebProxyFromEnvironmentVariablesGetProxyEmptyHttpProxy()
        {
            try
            {
                Environment.SetEnvironmentVariable("https_proxy", "http://user2:pass2%40@127.0.0.1:9999");
                var proxy = new RunnerWebProxy();

                Assert.Null(proxy.GetProxy(new Uri("http://github.com")));
                Assert.Null(proxy.GetProxy(new Uri("http://example.com:444")));

                Assert.Equal("http://user2:pass2%40@127.0.0.1:9999/", proxy.GetProxy(new Uri("https://something.com")).AbsoluteUri);
                Assert.Equal("http://user2:pass2%40@127.0.0.1:9999/", proxy.GetProxy(new Uri("https://www.something2.com")).AbsoluteUri);
            }
            finally
            {
                CleanProxyEnv();
            }
        }

        [Fact]
        [Trait("Level", "L0")]
        [Trait("Category", "Common")]
        public void WebProxyFromEnvironmentVariablesGetProxyEmptyHttpsProxy()
        {
            try
            {
                Environment.SetEnvironmentVariable("http_proxy", "http://user1:pass1%40@127.0.0.1:8888");
                var proxy = new RunnerWebProxy();

                Assert.Null(proxy.GetProxy(new Uri("https://github.com/owner/repo")));
                Assert.Null(proxy.GetProxy(new Uri("https://mails.google.com")));

                Assert.Equal("http://user1:pass1%40@127.0.0.1:8888/", proxy.GetProxy(new Uri("http://something.com")).AbsoluteUri);
                Assert.Equal("http://user1:pass1%40@127.0.0.1:8888/", proxy.GetProxy(new Uri("http://www.something2.com")).AbsoluteUri);
            }
            finally
            {
                CleanProxyEnv();
            }
        }

        [Fact]
        [Trait("Level", "L0")]
        [Trait("Category", "Common")]
        public void WebProxyFromEnvironmentVariablesNoProxy()
        {
            try
            {
                Environment.SetEnvironmentVariable("http_proxy", "http://user1:pass1%40@127.0.0.1:8888");
                Environment.SetEnvironmentVariable("https_proxy", "http://user2:pass2%40@127.0.0.1:9999");
                Environment.SetEnvironmentVariable("no_proxy", "github.com, .google.com, example.com:444, 192.168.0.123:123, 192.168.1.123");
                var proxy = new RunnerWebProxy();

                Assert.False(proxy.IsBypassed(new Uri("https://actions.com")));
                Assert.False(proxy.IsBypassed(new Uri("https://ggithub.com")));
                Assert.False(proxy.IsBypassed(new Uri("https://github.comm")));
                Assert.False(proxy.IsBypassed(new Uri("https://google.com"))); // no_proxy has '.google.com', specifying only subdomains bypass
                Assert.False(proxy.IsBypassed(new Uri("https://example.com")));
                Assert.False(proxy.IsBypassed(new Uri("http://example.com:333")));
                Assert.False(proxy.IsBypassed(new Uri("http://192.168.0.123:123")));
                Assert.False(proxy.IsBypassed(new Uri("http://192.168.1.123/home")));

                Assert.True(proxy.IsBypassed(new Uri("https://github.com")));
                Assert.True(proxy.IsBypassed(new Uri("https://GITHUB.COM")));
                Assert.True(proxy.IsBypassed(new Uri("https://github.com/owner/repo")));
                Assert.True(proxy.IsBypassed(new Uri("https://actions.github.com")));
                Assert.True(proxy.IsBypassed(new Uri("https://mails.google.com")));
                Assert.True(proxy.IsBypassed(new Uri("https://MAILS.GOOGLE.com")));
                Assert.True(proxy.IsBypassed(new Uri("https://mails.v2.google.com")));
                Assert.True(proxy.IsBypassed(new Uri("http://mails.v2.v3.google.com/inbox")));
                Assert.True(proxy.IsBypassed(new Uri("https://example.com:444")));
                Assert.True(proxy.IsBypassed(new Uri("http://example.com:444")));
                Assert.True(proxy.IsBypassed(new Uri("http://example.COM:444")));
            }
            finally
            {
                CleanProxyEnv();
            }
        }
        [Fact]
        [Trait("Level", "L0")]
        [Trait("Category", "Common")]
        public void BypassAllOnWildcardNoProxy()
        {
            try
            {
                Environment.SetEnvironmentVariable("http_proxy", "http://user1:pass1%40@127.0.0.1:8888");
                Environment.SetEnvironmentVariable("https_proxy", "http://user2:pass2%40@127.0.0.1:9999");
                Environment.SetEnvironmentVariable("no_proxy", "example.com, * , example2.com");
                var proxy = new RunnerWebProxy();

                Assert.True(proxy.IsBypassed(new Uri("http://actions.com")));
                Assert.True(proxy.IsBypassed(new Uri("http://localhost")));
                Assert.True(proxy.IsBypassed(new Uri("http://127.0.0.1:8080")));
                Assert.True(proxy.IsBypassed(new Uri("https://actions.com")));
                Assert.True(proxy.IsBypassed(new Uri("https://localhost")));
                Assert.True(proxy.IsBypassed(new Uri("https://127.0.0.1:8080")));
            }
            finally
            {
                CleanProxyEnv();
            }
        }

        [Fact]
        [Trait("Level", "L0")]
        [Trait("Category", "Common")]
        public void IgnoreWildcardInNoProxySubdomain()
        {
            try
            {
                Environment.SetEnvironmentVariable("http_proxy", "http://user1:pass1%40@127.0.0.1:8888");
                Environment.SetEnvironmentVariable("https_proxy", "http://user2:pass2%40@127.0.0.1:9999");
                Environment.SetEnvironmentVariable("no_proxy", "*.example.com");
                var proxy = new RunnerWebProxy();

                Assert.False(proxy.IsBypassed(new Uri("http://sub.example.com")));
                Assert.False(proxy.IsBypassed(new Uri("http://example.com")));
            }
            finally
            {
                CleanProxyEnv();
            }
        }

        [Fact]
        [Trait("Level", "L0")]
        [Trait("Category", "Common")]
        public void WildcardNoProxyWorksWhenOtherNoProxyAreAround()
        {
            try
            {
                Environment.SetEnvironmentVariable("http_proxy", "http://user1:pass1%40@127.0.0.1:8888");
                Environment.SetEnvironmentVariable("https_proxy", "http://user2:pass2%40@127.0.0.1:9999");
                Environment.SetEnvironmentVariable("no_proxy", "example.com,*,example2.com");
                var proxy = new RunnerWebProxy();

                Assert.True(proxy.IsBypassed(new Uri("http://actions.com")));
                Assert.True(proxy.IsBypassed(new Uri("http://localhost")));
                Assert.True(proxy.IsBypassed(new Uri("http://127.0.0.1:8080")));
                Assert.True(proxy.IsBypassed(new Uri("https://actions.com")));
                Assert.True(proxy.IsBypassed(new Uri("https://localhost")));
                Assert.True(proxy.IsBypassed(new Uri("https://127.0.0.1:8080")));
            }
            finally
            {
                CleanProxyEnv();
            }
        }

        [Fact]
        [Trait("Level", "L0")]
        [Trait("Category", "Common")]
        public void WebProxyFromEnvironmentVariablesGetProxy()
        {
            try
            {
                Environment.SetEnvironmentVariable("http_proxy", "http://user1:pass1%40@127.0.0.1:8888");
                Environment.SetEnvironmentVariable("https_proxy", "http://user2:pass2%40@127.0.0.1:9999");
                Environment.SetEnvironmentVariable("no_proxy", "github.com, .google.com, example.com:444");
                var proxy = new RunnerWebProxy();

                Assert.Null(proxy.GetProxy(new Uri("http://github.com")));
                Assert.Null(proxy.GetProxy(new Uri("https://github.com/owner/repo")));
                Assert.Null(proxy.GetProxy(new Uri("https://mails.google.com")));
                Assert.Null(proxy.GetProxy(new Uri("http://example.com:444")));


                Assert.Equal("http://user1:pass1%40@127.0.0.1:8888/", proxy.GetProxy(new Uri("http://something.com")).AbsoluteUri);
                Assert.Equal("http://user1:pass1%40@127.0.0.1:8888/", proxy.GetProxy(new Uri("http://www.something2.com")).AbsoluteUri);

                Assert.Equal("http://user2:pass2%40@127.0.0.1:9999/", proxy.GetProxy(new Uri("https://something.com")).AbsoluteUri);
                Assert.Equal("http://user2:pass2%40@127.0.0.1:9999/", proxy.GetProxy(new Uri("https://www.something2.com")).AbsoluteUri);
            }
            finally
            {
                CleanProxyEnv();
            }
        }

        [Fact]
        [Trait("Level", "L0")]
        [Trait("Category", "Common")]
        public void WebProxyFromEnvironmentVariablesWithPort80()
        {
            try
            {
                Environment.SetEnvironmentVariable("http_proxy", "http://127.0.0.1:80");
                Environment.SetEnvironmentVariable("https_proxy", "http://user:pass@127.0.0.1:80");
                Environment.SetEnvironmentVariable("no_proxy", "github.com, google.com,");
                var proxy = new RunnerWebProxy();

                Assert.Equal("http://127.0.0.1:80", Environment.GetEnvironmentVariable("http_proxy"));
                Assert.Null(proxy.HttpProxyUsername);
                Assert.Null(proxy.HttpProxyPassword);

                Assert.Equal("http://user:pass@127.0.0.1:80", Environment.GetEnvironmentVariable("https_proxy"));
                Assert.Equal("user", proxy.HttpsProxyUsername);
                Assert.Equal("pass", proxy.HttpsProxyPassword);

                Assert.Equal(2, proxy.NoProxyList.Count);
                Assert.Equal("github.com", proxy.NoProxyList[0].Host);
                Assert.Equal("google.com", proxy.NoProxyList[1].Host);
            }
            finally
            {
                CleanProxyEnv();
            }
        }

        private void CleanProxyEnv()
        {
            Environment.SetEnvironmentVariable("http_proxy", null);
            Environment.SetEnvironmentVariable("https_proxy", null);
            Environment.SetEnvironmentVariable("HTTP_PROXY", null);
            Environment.SetEnvironmentVariable("HTTPS_PROXY", null);
            Environment.SetEnvironmentVariable("no_proxy", null);
            Environment.SetEnvironmentVariable("NO_PROXY", null);
        }
    }
}
