using FastGithub.Configuration;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
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 domainResolveCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
        private readonly IMemoryCache disableIPAddressCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
        private readonly DnscryptProxy dnscryptProxy;
        private readonly ILogger logger;
        private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(5d);
        private readonly TimeSpan disableIPExpiration = TimeSpan.FromMinutes(2d);
        private readonly TimeSpan dnscryptExpiration = TimeSpan.FromMinutes(10d);
        private readonly TimeSpan fallbackExpiration = TimeSpan.FromMinutes(2d);
        private readonly TimeSpan loopbackExpiration = TimeSpan.FromSeconds(5d);
        private readonly IPEndPoint fallbackDns = new(IPAddress.Parse("114.114.114.114"), 53);
        private readonly ConcurrentDictionary semaphoreSlims = new();
        /// 
        /// 域名解析器
        ///   
        /// 
        /// 
        public DomainResolver(
            DnscryptProxy dnscryptProxy,
            ILogger logger)
        {
            this.dnscryptProxy = dnscryptProxy;
            this.logger = logger;
        }
        /// 
        /// 设置ip不可用
        /// 
        /// ip
        public void SetDisabled(IPAddress address)
        {
            this.disableIPAddressCache.Set(address, address, this.disableIPExpiration);
        }
        /// 
        /// 刷新域名解析结果
        /// 
        /// 域名
        public void FlushDomain(DnsEndPoint domain)
        {
            this.domainResolveCache.Remove(domain);
        }
        /// 
        /// 解析域名
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        public async Task ResolveAsync(DnsEndPoint domain, CancellationToken cancellationToken = default)
        {
            var semaphore = this.semaphoreSlims.GetOrAdd(domain, _ => new SemaphoreSlim(1, 1));
            try
            {
                await semaphore.WaitAsync();
                return await this.ResolveCoreAsync(domain, cancellationToken);
            }
            finally
            {
                semaphore.Release();
            }
        }
        /// 
        /// 解析域名
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        private async Task ResolveCoreAsync(DnsEndPoint domain, CancellationToken cancellationToken)
        {
            if (this.domainResolveCache.TryGetValue(domain, out var address) && address != null)
            {
                return address;
            }
            var expiration = this.dnscryptExpiration;
            address = await this.LookupByDnscryptAsync(domain, cancellationToken);
            if (address == null)
            {
                address = await this.LookupByDnscryptAsync(domain, cancellationToken);
            }
            if (address == null)
            {
                expiration = this.fallbackExpiration;
                address = await this.LookupByFallbackAsync(domain, cancellationToken);
            }
            if (address == null)
            {
                throw new FastGithubException($"当前解析不到{domain.Host}可用的ip,请刷新重试");
            }
            // 往往是被污染的dns
            if (address.Equals(IPAddress.Loopback) == true)
            {
                expiration = this.loopbackExpiration;
            }
            this.domainResolveCache.Set(domain, address, expiration);
            return address;
        }
        /// 
        /// Dnscrypt查找ip
        /// 
        /// 
        /// 
        /// 
        private async Task LookupByDnscryptAsync(DnsEndPoint domain, CancellationToken cancellationToken)
        {
            var dns = this.dnscryptProxy.LocalEndPoint;
            if (dns == null)
            {
                return null;
            }
            var dnsClient = new DnsClient(dns, forceTcp: false);
            return await this.LookupAsync(dnsClient, domain, cancellationToken);
        }
        /// 
        /// 回退查找ip
        ///  
        /// 
        /// 
        /// 
        ///         
        private async Task LookupByFallbackAsync(DnsEndPoint domain, CancellationToken cancellationToken)
        {
            var dnsClient = new DnsClient(this.fallbackDns, forceTcp: true);
            return await this.LookupAsync(dnsClient, domain, cancellationToken);
        }
        /// 
        /// 查找ip
        /// 
        /// 
        /// 
        /// 
        /// 
        private async Task LookupAsync(DnsClient dnsClient, DnsEndPoint domain, CancellationToken cancellationToken)
        {
            try
            {
                var addresses = await dnsClient.LookupAsync(domain.Host, cancellationToken);
                var address = await this.FindFastValueAsync(addresses, domain.Port, cancellationToken);
                if (address == null)
                {
                    this.logger.LogWarning($"dns({dnsClient})解析不到{domain.Host}可用的ip解析");
                }
                else
                {
                    this.logger.LogInformation($"dns({dnsClient}): {domain.Host}->{address}");
                }
                return address;
            }
            catch (Exception ex)
            {
                cancellationToken.ThrowIfCancellationRequested();
                this.logger.LogWarning($"dns({dnsClient})无法解析{domain.Host}:{ex.Message}");
                return default;
            }
        }
        /// 
        /// 获取最快的ip
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        private async Task FindFastValueAsync(IEnumerable addresses, int port, CancellationToken cancellationToken)
        {
            addresses = addresses.Where(IsEnableIPAddress).ToArray();
            if (addresses.Any() == false)
            {
                return default;
            }
            if (port <= 0)
            {
                return addresses.FirstOrDefault();
            }
            var tasks = addresses.Select(address => this.IsAvailableAsync(address, port, cancellationToken));
            var fastTask = await Task.WhenAny(tasks);
            return await fastTask;
            bool IsEnableIPAddress(IPAddress address)
            {
                return this.disableIPAddressCache.TryGetValue(address, out _) == false;
            }
        }
        /// 
        /// 验证远程节点是否可连接
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        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)
            {
                cancellationToken.ThrowIfCancellationRequested();
                this.SetDisabled(address);
                return default;
            }
            catch (Exception)
            {
                this.SetDisabled(address);
                await Task.Delay(this.connectTimeout, cancellationToken);
                return default;
            }
        }
    }
}