using DNS.Client;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.Scanner.LookupProviders
{
    /// 
    /// 公共dns的域名与ip关系提供者
    /// 
    [Service(ServiceLifetime.Singleton, ServiceType = typeof(IGithubLookupProvider))]
    sealed class PublicDnsProvider : IGithubLookupProvider
    {
        private readonly IOptionsMonitor options;
        private readonly ILogger logger;
        /// 
        /// 获取排序
        /// 
        public int Order => default;
        /// 
        /// 公共dns的域名与ip关系提供者
        /// 
        /// 
        /// 
        public PublicDnsProvider(
            IOptionsMonitor options,
            ILogger logger)
        {
            this.options = options;
            this.logger = logger;
        }
        /// 
        /// 查找域名与ip关系
        /// 
        /// 
        /// 
        /// 
        public async Task> LookupAsync(IEnumerable domains, CancellationToken cancellationToken)
        {
            var setting = this.options.CurrentValue;
            if (setting.Enable == false)
            {
                return Enumerable.Empty();
            }
            var result = new HashSet();
            foreach (var dns in setting.Dnss)
            {
                var domainAddresses = await this.LookupAsync(dns, domains, cancellationToken);
                foreach (var item in domainAddresses)
                {
                    result.Add(item);
                }
            }
            return result;
        }
        /// 
        /// 反查ip
        /// 
        /// dns服务器
        /// 域名
        /// 
        private async Task> LookupAsync(string dns, IEnumerable domains, CancellationToken cancellationToken)
        {
            var client = new DnsClient(dns);
            var result = new List();
            foreach (var domain in domains)
            {
                try
                {
                    using var timeoutTokenSource = new CancellationTokenSource(this.options.CurrentValue.Timeout);
                    using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutTokenSource.Token, cancellationToken);
                    var addresses = await client.Lookup(domain, cancellationToken: linkedTokenSource.Token);
                    foreach (var address in addresses)
                    {
                        if (address.AddressFamily == AddressFamily.InterNetwork)
                        {
                            result.Add(new DomainAddress(domain, address));
                        }
                    }
                }
                catch (Exception)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    this.logger.LogWarning($"dns({dns})无法解析{domain}");
                }
            }
            return result;
        }
    }
}