using DNS.Client;
using DNS.Protocol;
using FastGithub.Configuration;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.DomainResolve
{
    /// 
    /// 域名解析器
    ///  
    sealed class DomainResolver : IDomainResolver
    {
        private readonly IMemoryCache memoryCache;
        private readonly FastGithubConfig fastGithubConfig;
        private readonly ILogger logger;
        private readonly TimeSpan lookupTimeout = TimeSpan.FromSeconds(2d);
        private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(2d);
        private readonly TimeSpan resolveCacheTimeSpan = TimeSpan.FromMinutes(2d);
        private readonly ConcurrentDictionary semaphoreSlims = new();
        /// 
        /// 域名解析器
        /// 
        /// 
        /// 
        /// 
        public DomainResolver(
            IMemoryCache memoryCache,
            FastGithubConfig fastGithubConfig,
            ILogger logger)
        {
            this.memoryCache = memoryCache;
            this.fastGithubConfig = fastGithubConfig;
            this.logger = logger;
        }
        /// 
        /// 解析域名
        /// 
        /// 
        /// 
        /// 
        public async Task ResolveAsync(DnsEndPoint endPoint, CancellationToken cancellationToken = default)
        {
            var semaphore = this.semaphoreSlims.GetOrAdd(endPoint, _ => new SemaphoreSlim(1, 1));
            try
            {
                await semaphore.WaitAsync(cancellationToken);
                if (this.memoryCache.TryGetValue(endPoint, out var address) == false)
                {
                    address = await this.LookupAsync(endPoint, cancellationToken);
                    this.memoryCache.Set(endPoint, address, this.resolveCacheTimeSpan);
                }
                return address;
            }
            finally
            {
                semaphore.Release();
            }
        }
        /// 
        /// 查找ip
        /// 
        /// 
        /// 
        /// 
        private async Task LookupAsync(DnsEndPoint endPoint, CancellationToken cancellationToken)
        {
            var pureDns = this.fastGithubConfig.PureDns;
            var fastDns = this.fastGithubConfig.FastDns;
            try
            {
                return await LookupCoreAsync(pureDns, endPoint, cancellationToken);
            }
            catch (Exception)
            {
                this.logger.LogWarning($"由于{pureDns}解析{endPoint.Host}失败,本次使用{fastDns}");
                return await LookupCoreAsync(fastDns, endPoint, cancellationToken);
            }
        }
        /// 
        /// 查找ip
        /// 
        /// 
        /// 
        /// 
        /// 
        private async Task LookupCoreAsync(IPEndPoint dns, DnsEndPoint endPoint, CancellationToken cancellationToken)
        {
            var dnsClient = new DnsClient(dns);
            using var timeoutTokenSource = new CancellationTokenSource(this.lookupTimeout);
            using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token);
            var addresses = await dnsClient.Lookup(endPoint.Host, RecordType.A, linkedTokenSource.Token);
            var fastAddress = await this.GetFastIPAddressAsync(addresses, endPoint.Port, cancellationToken);
            if (fastAddress != null)
            {
                this.logger.LogInformation($"[{endPoint.Host}->{fastAddress}]");
                return fastAddress;
            }
            throw new FastGithubException($"dns{dns}解析不到{endPoint.Host}可用的ip");
        }
        /// 
        /// 获取最快的ip
        /// 
        /// 
        /// 
        /// 
        /// 
        private async Task GetFastIPAddressAsync(IEnumerable addresses, int port, CancellationToken cancellationToken)
        {
            var tasks = addresses.Select(address => this.IsAvailableAsync(address, port, cancellationToken));
            var fastTask = await Task.WhenAny(tasks);
            return await fastTask;
        }
        /// 
        /// 验证远程节点是否可连接
        /// 
        /// 
        /// 
        /// 
        /// 
        private async Task IsAvailableAsync(IPAddress address, int port, CancellationToken cancellationToken)
        {
            try
            {
                using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
                using var timeoutTokenSource = new CancellationTokenSource(this.connectTimeout);
                using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token);
                await socket.ConnectAsync(address, port, linkedTokenSource.Token);
                return address;
            }
            catch (OperationCanceledException)
            {
                return default;
            }
            catch (Exception)
            {
                await Task.Delay(this.connectTimeout, cancellationToken);
                return default;
            }
        }
    }
}