using System.Net;
using System.Net.Mail;
using System.Net.Mime;
using e_suite.API.Common.exceptions;
using e_suite.API.Common.repository;
using e_suite.Database.Audit;
using e_suite.Database.Audit.AuditEngine;
using e_suite.Service.Mail.Facade;
using e_suite.Service.Mail.Helper;
using eSuite.Core.MailService;
using Microsoft.Extensions.Configuration;

namespace e_suite.Service.Mail;

public class MailService : IMailService
{
    private readonly IConfiguration _configuration;
    private readonly IUserManagerRepository _userManagerRepository;
    private readonly IMailServiceRepository _mailServiceRepository;
    private readonly ISmtpClientFactory _smtpClientFactory;
    const long SunStrategyDomainId = 1;

    public MailService(IConfiguration configuration, IUserManagerRepository userManagerRepository, IMailServiceRepository mailServiceRepository, ISmtpClientFactory smtpClientFactory)
    {
        _configuration = configuration;
        _userManagerRepository = userManagerRepository;
        _mailServiceRepository = mailServiceRepository;
        _smtpClientFactory = smtpClientFactory;
    }

    public async Task RequestEMailAsync(MailRequest emailRequest, CancellationToken cancellationToken)
    {
        var username = _configuration.GetConfigValue("MAIL_USERNAME", "Smtp:Username", string.Empty);
        var host = _configuration.GetConfigValue("MAIL_SERVER", "Smtp:Server", "defaultmailserver");

        var port = _configuration.GetConfigValue("MAIL_PORT", "Smtp:Port", 25);
        var fromAddress = _configuration.GetConfigValue("MAIL_ADDRESS", "Smtp:FromAddress", "defaultFromAddress");
        var fromDisplayName = _configuration.GetConfigValue("MAIL_DISPLAY_NAME","Smtp:FromDisplayName", "eSuite MailService");

        foreach (var address in emailRequest.To)
        {
            var message = new MailMessage
            {
                Body = null,
                BodyEncoding = null,
                BodyTransferEncoding = TransferEncoding.QuotedPrintable,
                DeliveryNotificationOptions = DeliveryNotificationOptions.None,
                From = new MailAddress(fromAddress!, fromDisplayName),
                HeadersEncoding = null,
                IsBodyHtml = false,
                Priority = MailPriority.Normal,
                Sender = null!,
                Subject = null,
                SubjectEncoding = null,
            };

            var user = await _userManagerRepository.GetUserByEmail(address.Email, cancellationToken) ??
                       throw new NotFoundException($"Unable to find user for e-mail {address.Email}");

            message.To.Add(new MailAddress(address.Email, address.DisplayName));

            await PrepareMessageBody(message, emailRequest, user.DomainId, cancellationToken);

            using var client = _smtpClientFactory.CreateSmtpClient(host, port);

            if (!string.IsNullOrEmpty(username))
            {
                var password = _configuration.GetConfigValue("MAIL_PASSWORD", "Smtp:Password", string.Empty);
                client.Credentials = new NetworkCredential(username, password);
            }

            client.EnableSsl = _configuration.GetValue("Smtp:EnableSSL", false);
            client.Timeout = _configuration.GetValue("Smtp:timeout", 30000);

#pragma warning disable SYSLIB0014
            //There is no option be to use the ServicePointManager at the moment.
            ServicePointManager.ServerCertificateValidationCallback = CertificateValidationCallBack!;
#pragma warning restore SYSLIB0014

            try
            {
                await client.SendMailAsync(message, cancellationToken);
                var auditUserDetails = new AuditUserDetails
                {
                    UserDisplayName = "Mail service"
                };

                var fields = new Dictionary<string, Change>
                {
                    ["Address"] = new()
                    {
                        NewValue = address.Email,
                    },
                    ["DisplayName"] = new()
                    {
                        NewValue = address.DisplayName
                    },
                    ["EmailType"] = new()
                    {
                        NewValue = emailRequest.EmailType
                    },
                    ["Subject"] = new()
                    {
                        NewValue = message.Subject
                    }
                };
                await _userManagerRepository.AddAdhocAuditEntry(auditUserDetails, AuditType.EmailSent, fields, cancellationToken);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
    }

    private async Task PrepareMessageBody(MailMessage message, MailRequest emailRequest, long domainId,
        CancellationToken cancellationToken)
    {
        message.IsBodyHtml = true;

        var mailTemplate =
            await _mailServiceRepository.GetMailTemplate(domainId, emailRequest.EmailType, cancellationToken) ?? await _mailServiceRepository.GetMailTemplate(SunStrategyDomainId, emailRequest.EmailType,
                cancellationToken) ??
            throw new NotImplementedException($"{emailRequest.EmailType} not implemented");

        message.Subject = mailTemplate.Subject;
        message.Body = $"<html><head></head><body>{mailTemplate.TemplateDefinition}<body></html>";

        SubstituteParameters(message, emailRequest);

    }

    private static void SubstituteParameters(MailMessage message, MailRequest emailRequest)
    {
        foreach (var parameter in emailRequest.Parameters)
            message.Body = message.Body.Replace($"${parameter.Key}", parameter.Value, StringComparison.InvariantCultureIgnoreCase);
    }

    /// <summary>
    /// https://docs.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2010/dd633677(v=exchg.80)?redirectedfrom=MSDN
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="certificate"></param>
    /// <param name="chain"></param>
    /// <param name="sslPolicyErrors"></param>
    /// <returns></returns>
    private static bool CertificateValidationCallBack(
        object sender,
        System.Security.Cryptography.X509Certificates.X509Certificate certificate,
        System.Security.Cryptography.X509Certificates.X509Chain chain,
        System.Net.Security.SslPolicyErrors sslPolicyErrors)
    {
        // If the certificate is a valid, signed certificate, return true.
        if (sslPolicyErrors == System.Net.Security.SslPolicyErrors.None) return true;

        // If there are errors in the certificate chain, look at each error to determine the cause.
        if ((sslPolicyErrors & System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors) != 0)
        {
            if (chain != null && chain.ChainStatus != null)
                foreach (var status in chain.ChainStatus)
                    if (certificate.Subject == certificate.Issuer &&
                        status.Status == System.Security.Cryptography.X509Certificates.X509ChainStatusFlags
                            .UntrustedRoot)
                    {
                        // Self-signed certificates with an untrusted root are valid. 
                        continue;
                    }
                    else
                    {
                        if (status.Status != System.Security.Cryptography.X509Certificates.X509ChainStatusFlags
                                .NoError)
                            // If there are any other errors in the certificate chain, the certificate is invalid,
                            // so the method returns false.
                            return false;
                    }

            // When processing reaches this line, the only errors in the certificate chain are 
            // untrusted root errors for self-signed certificates. These certificates are valid
            // for default Exchange server installations, so return true.
            return true;
        }
        else
        {
            // In all other cases, return false.
            return false;
        }
    }
}