using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.Scanner.ScanMiddlewares
{
    /// 
    /// https扫描中间件
    /// 
    [Service(ServiceLifetime.Singleton)]
    sealed class HttpsScanMiddleware : IMiddleware
    {
        private readonly IOptionsMonitor options;
        private readonly IHttpClientFactory httpClientFactory;
        private readonly ILogger logger;
        /// 
        /// https扫描中间件
        /// 
        /// 
        /// 
        public HttpsScanMiddleware(
            IOptionsMonitor options,
            IHttpClientFactory httpClientFactory,
            ILogger logger)
        {
            this.options = options;
            this.httpClientFactory = httpClientFactory;
            this.logger = logger;
        }
        /// 
        /// https扫描
        /// 
        /// 
        /// 
        /// 
        public async Task InvokeAsync(GithubContext context, Func next)
        {
            try
            {
                context.Available = false;
                var setting = this.options.CurrentValue;
                if (setting.Rules.TryGetValue(context.Domain, out var rule) == false)
                {
                    rule = new HttpsScanOptions.ScanRule();
                }
                using var request = new HttpRequestMessage();
                request.Method = new HttpMethod(rule.Method);
                request.RequestUri = new Uri(new Uri($"http://{context.Address}:443/"), rule.Path);
                request.Headers.Host = context.Domain;
                request.Headers.ConnectionClose = setting.ConnectionClose;
                var timeout = this.options.CurrentValue.Timeout;
                using var timeoutTokenSource = new CancellationTokenSource(timeout);
                using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutTokenSource.Token, context.CancellationToken);
                var httpClient = this.httpClientFactory.CreateClient(nameof(Scanner));
                using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, linkedTokenSource.Token);
                VerifyHttpsResponse(context.Domain, response);
                context.Available = true;
                await next();
            }
            catch (Exception ex)
            {
                context.CancellationToken.ThrowIfCancellationRequested();
                this.logger.LogTrace($"{context.Domain} {context.Address} { GetInnerMessage(ex)}");
            }
        }
        /// 
        /// 验证响应内容
        /// 
        /// 
        /// 
        /// 
        /// 
        private static void VerifyHttpsResponse(string domain, HttpResponseMessage response)
        {
            response.EnsureSuccessStatusCode();
            if (domain == "github.com" || domain.EndsWith(".github.com"))
            {
                if (response.Headers.Server.Any(item => IsGithubServer(item)) == false)
                {
                    throw new ValidationException("伪造的github服务");
                }
            }
            static bool IsGithubServer(ProductInfoHeaderValue headerValue)
            {
                var value = headerValue.Product?.Name;
                return string.Equals("github.com", value, StringComparison.OrdinalIgnoreCase);
            }
        }
        private static string GetInnerMessage(Exception ex)
        {
            while (ex.InnerException != null)
            {
                return GetInnerMessage(ex.InnerException);
            }
            return ex.Message;
        }
    }
}