using System.Linq;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http.Dispatchers;
using NzbDrone.Core.Configuration;

namespace NzbDrone.Core.Security
{
    public class X509CertificateValidationService : ICertificateValidationService
    {
        private readonly IConfigService _configService;
        private readonly Logger _logger;

        public X509CertificateValidationService(IConfigService configService, Logger logger)
        {
            _configService = configService;
            _logger = logger;
        }

        public bool ShouldByPassValidationError(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            var targetHostName = string.Empty;

            if (sender is not SslStream && sender is not string)
            {
                return true;
            }

            if (sender is SslStream request)
            {
                targetHostName = request.TargetHostName;
            }

            // Mailkit passes host in sender as string
            if (sender is string stringHost)
            {
                targetHostName = stringHost;
            }

            if (certificate is X509Certificate2 cert2 && cert2.SignatureAlgorithm.FriendlyName == "md5RSA")
            {
                _logger.Error("https://{0} uses the obsolete md5 hash in it's https certificate, if that is your certificate, please (re)create certificate with better algorithm as soon as possible.", targetHostName);
            }

            if (sslPolicyErrors == SslPolicyErrors.None)
            {
                return true;
            }

            if (targetHostName == "localhost" || targetHostName == "127.0.0.1")
            {
                return true;
            }

            var ipAddresses = GetIPAddresses(targetHostName);
            var certificateValidation = _configService.CertificateValidation;

            if (certificateValidation == CertificateValidationType.Disabled)
            {
                return true;
            }

            if (certificateValidation == CertificateValidationType.DisabledForLocalAddresses &&
                ipAddresses.All(i => i.IsIPv6LinkLocal || i.IsLocalAddress()))
            {
                return true;
            }

            _logger.Error("Certificate validation for {0} failed. {1}", targetHostName, sslPolicyErrors);

            return false;
        }

        private IPAddress[] GetIPAddresses(string host)
        {
            if (IPAddress.TryParse(host, out var ipAddress))
            {
                return new[] { ipAddress };
            }

            return Dns.GetHostEntry(host).AddressList;
        }
    }
}
