using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml.Serialization;
using System.Collections;
using System.IO;

using Vrh.XmlProcessing;
using VRH.Common;

namespace Vrh.OneMessage
{
    /// <summary>
    /// Üzenetkézbesítő komponens. MVC controller nélkül is hívható a SendMessage metódus. 
    /// A paraméterek feloldása a konstruktorban végbemegy a ReceivedParameters típusban megadott adatok alapján.
    /// </summary>
    public class OneMessage
    {
        #region Properties

        /// <summary>
        /// Az XML-ben lévő OneMessage szerkezetű block azonosítója.
        /// </summary>
        public string MessageId { get; set; }

        /// <summary>
        /// Email szerverhez való kapcsolódás adatai
        /// </summary>
        public EmailServerConnection Connection { get; set; }

        /// <summary>
        /// A feladó. Ki küldi az üzenetet.
        /// </summary>
        public string SendAs { get; set; }

        /// <summary>
        /// Címzettek listája. Kinek küldjük az üzenetet. 
        /// </summary>
        public List<string> SendTo { get; set; }

        /// <summary>
        /// Címzettek listája. Kinek küldünk másolatot az üzenetből.
        /// </summary>
        public List<string> SendCopyTo { get; set; }

        /// <summary>
        /// Az üzenet tárgya.
        /// </summary>
        public string Subject { get; set; }

        /// <summary>
        /// Az üzenet tartalma.
        /// </summary>
        public string Body { get; set; }

        /// <summary>
        /// Csatolni kívánt fájlok neveinek listája.
        /// </summary>
        public List<string> Attachments { get; set; }

        /// <summary>
        /// A csatolt fájlok törlődjenek-e a sikeres küldés után.
        /// </summary>
        public bool IsDeleteAfterSend { get; set; }

        /// <summary>
        /// Feldolgozás közben keletkezett hibaüzenetek listája.
        /// </summary>
        public List<string> ErrorList { get; private set; } = new List<string>();

        #endregion Properties

        #region Constructor
        /// <summary>
        /// OneMessage komponens konstruktora.
        /// A <paramref name="pars"/> paraméterben a 'MessageId' paraméter megadása szükséges.
        /// </summary>
        /// <param name="pars">Az osztály számra szükséges paramétereket tartalmazó típus.</param>
        /// <exception cref="System.ApplicationException">
        /// Ha az xml + paraméterek feldolgozása közben hibát észlel.
        /// </exception>
        public OneMessage(OneMessageParameters pars)
        {
            try
            {
                #region DEBUG
#if DEBUG
                Debug.WriteLine($"OneMessage parameters:");
                foreach (KeyValuePair<string, string> up in pars.Parameters) Debug.WriteLine($"{up.Key}={up.Value}");
#endif
                #endregion DEBUG

                if (string.IsNullOrWhiteSpace(pars.MessageId))
                {
                    throw new ApplicationException($"The OneMessageParameters.MessageId is null or empty!");

                }
                this.MessageId = pars.MessageId;

                var xmlcs = pars.XmlConnectionString;
                if (string.IsNullOrWhiteSpace(xmlcs)) { xmlcs = $"config={Constants.DEFAULT_CONFIGNAME};"; }
                var xmlc = new XmlConnection(xmlcs);
                using (var omxp = new OneMessageXmlParser(xmlc, pars.AppPath, pars.LCID, pars.Parameters, pars.MessageId, pars.Delivery))
                {
					#region Combining external parameters with xml structure

					#region Connection create

					string cs = string.IsNullOrEmpty(pars.ConnectionString) ? omxp.Delivery.ConnectionStringName : pars.ConnectionString;
					string emailserverConnectionString=null;
					try { emailserverConnectionString = ConnectionStringStore.GetEmailServer(cs); } catch { emailserverConnectionString = cs; }
                    this.Connection = new EmailServerConnection(emailserverConnectionString);

                    #endregion Connection create

                    this.SendAs = string.IsNullOrEmpty(pars.SendAs) ? omxp.Delivery.SendAs : pars.SendAs;

                    #region SendCopyTo parser

                    string sendcopyto = string.IsNullOrEmpty(pars.SendCopyTo) ? omxp.Delivery.SendCopyTo : pars.SendCopyTo;
                    this.SendCopyTo = new List<string>();
                    foreach (string addr in sendcopyto.Split(',').Where(x => !string.IsNullOrEmpty(x)))
                    {
                        this.SendCopyTo.Add(addr);
                    }

                    #endregion SendCopyTo parser

                    #region SendTo parser

                    string sendto = string.IsNullOrEmpty(pars.SendTo) ? omxp.SendTo : pars.SendTo;
                    this.SendTo = new List<string>();
                    foreach (string addr in sendto.Split(',').Where(x => !string.IsNullOrEmpty(x)))
                    {
                        this.SendTo.Add(addr);
                    }

                    #endregion SendTo parser

                    if (!this.SendCopyTo.Any() && !this.SendTo.Any())
                    {
                        throw new ApplicationException("There are no recipients! The sendcopyto and sendto properties are empty!");
                    }

                    #region Subject parser

                    this.Subject = string.IsNullOrEmpty(pars.Subject) ? omxp.Subject : pars.Subject;

                    #endregion Subject parser

                    #region Body parser

                    this.Body = string.IsNullOrEmpty(pars.MessageBody) ? omxp.MessageBody : pars.MessageBody;

                    #endregion Body parser

                    #region  Attachments parser

                    this.Attachments = new List<string>();
                    this.IsDeleteAfterSend = false;
                    if (string.IsNullOrEmpty(pars.Attachments))
                    {   //ha üres a fogadott lista, akkor kielemezzük az xml-be írt listát
                        if (omxp.Attachments != null && omxp.Attachments.Any())
                        {
                            this.IsDeleteAfterSend = omxp.IsDeleteAfterSend;
                            foreach (var attach in omxp.Attachments)
                            {
                                if (attach.Folder.StartsWith("~"))
                                {
                                    attach.Folder = Path.Combine(
                                        pars.AppPath,
                                        attach.Folder.Trim()
                                              .Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar)
                                              .TrimStart('~', Path.DirectorySeparatorChar));
                                }
                                this.Attachments.Add(Path.Combine(attach.Folder, attach.File));
                            }
                        }
                    }
                    else
                    {   //ha nem üres a fogadott lista, akkor azok lesznek a csatolmányok
                        this.IsDeleteAfterSend = omxp.IsDeleteAfterSend;

                        string[] atts = pars.Attachments.Split(',');
                        foreach (string attachment in atts.Where(x => !string.IsNullOrEmpty(x)))
                        {
                            string att = attachment.StartsWith("~")
                                ? Path.Combine(pars.AppPath,
                                        attachment.Trim()
                                                  .Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar)
                                                  .TrimStart('~', Path.DirectorySeparatorChar))
                                : attachment;
                            this.Attachments.Add(att);
                        }
                    }
                    #endregion  Attachments parser

                    #endregion Combining external parameters with xml structure
                }
            }
            catch (Exception ex)
            {
                AddError(ex);
            }
        }
        #endregion Constructor

        #region Public Methods - SendMessage

        /// <summary>
        /// Az üzenet elküldése a típusban beállított adatok alapján.
        /// Ha a feldolgozás során hiba történt (nem üres az ErrorList), és úgy hívódik meg, akkor futás idejű hiba keletkezik.
        /// </summary>
        public void SendMessage()
        {
            if (this.ErrorList.Any())
            {
                throw new ApplicationException(String.Concat("The delivery is not possible because an error occurred while processing. ", string.Join(";", this.ErrorList)));
            }

            //if (om.Connection.Port == 465) SendMessageImplicit(om);
            //else SendMessageExplicit(om);
            SendMessageExplicit();

            //ha engedélyezték a törlést és van csatolmány, akkor megkísérli kitörölni.
            if (this.IsDeleteAfterSend && this.Attachments.Any())
            {
                foreach (string filename in this.Attachments)
                {
                    if (File.Exists(filename))
                    {
                        System.Diagnostics.Debug.WriteLine("File '{0}' exists.", filename, null);
                        int attempt = 5;
                        while (attempt > 0)
                        {
                            try
                            {
                                File.Delete(filename);
                                attempt = 0;
                            }
                            catch (Exception ex)
                            {
                                System.Diagnostics.Debug.WriteLine("Próba={0} " + ex.Message, 6 - attempt);
                                System.Threading.Thread.Sleep(200);
                                attempt--;
                            }
                        }
                    }
                }
            }
        }
        #endregion Public Methods - SendMessage

        #region SendMessageExplicit private method
        /// <summary>
        /// Explicit SSL-t támogató küldés a System.Net.Smtp-vel.
        /// </summary>
        private void SendMessageExplicit()
        {
            using (var mail = new System.Net.Mail.MailMessage())
            {
                mail.From = new System.Net.Mail.MailAddress(this.SendAs);
                foreach (string item in this.SendTo) mail.To.Add(item);
                foreach (string item in this.SendCopyTo) mail.CC.Add(item);
                mail.Subject = this.Subject;
                mail.SubjectEncoding = System.Text.Encoding.UTF8;
                mail.Body = this.Body;
                mail.BodyEncoding = System.Text.Encoding.UTF8;
                foreach (string filename in this.Attachments) mail.Attachments.Add(new System.Net.Mail.Attachment(filename));

                using (var client = new System.Net.Mail.SmtpClient())
                {
                    System.Diagnostics.Debug.WriteLine("SendMessageE1 Host={0}, Port={1}, SSL={2}, DefCre={3}, UserName={4}", client.Host, client.Port, client.EnableSsl, client.UseDefaultCredentials, this.Connection.UserName);
                    // System.Net.Mail.SmtpDeliveryMethod.Network = 0
                    // System.Net.Mail.SmtpDeliveryMethod.SpecifiedPickupDirectory = 1 
                    // System.Net.Mail.SmtpDeliveryMethod.PickupDirectoryFromIis = 2 
                    client.DeliveryMethod = System.Net.Mail.SmtpDeliveryMethod.Network;
                    client.Host = this.Connection.Host;
                    client.Port = this.Connection.Port;
                    client.EnableSsl = this.Connection.EnableSsl;
                    if (!String.IsNullOrEmpty(this.Connection.UserName))
                    {   //!!!ha beállítunk jelszót, akkor a UseDefaultCredentials, automatikusan falsra-vált!!!
                        client.Credentials = new System.Net.NetworkCredential(this.Connection.UserName, this.Connection.Password);
                    }
                    else
                    {
                        client.UseDefaultCredentials = this.Connection.UseDefaultCredentials;   //default = false
                    }
                    System.Diagnostics.Debug.WriteLine("SendMessageE2 Host={0}, Port={1}, SSL={2}, DefCre={3}, UserName={4}", client.Host, client.Port, client.EnableSsl, client.UseDefaultCredentials, this.Connection.UserName);
                    client.Send(mail);
                }
            }
        }
        #endregion SendMessageExplicit private method

        #region SendMessageImplicit (comment only) 
        ///// <summary>
        ///// Itt találtam ezt a megoldást.
        ///// https://forums.asp.net/t/1936687.aspx?Still+no+support+for+implicit+SSL+for+the+SmtpClient
        ///// És hiába nem támogatott típus, a Microsoft-tól nem találtam rá más megoldást.
        ///// </summary>
        ///// <remarks>
        ///// Nem használom mégse, mert:
        ///// 1. Nem támogatott! 
        ///// 2. Ha nincs tanúsítványa a szervernek, akkor nem kommunikál.
        ///// </remarks>
        ///// <param name="om"></param>
        //private void SendMessageImplicit(OneMessage om)
        //{
        //    string rooturl = @"http://schemas.microsoft.com/cdo/configuration/";
        //    System.Web.Mail.MailMessage myMail = new System.Web.Mail.MailMessage();
        //    myMail.Fields.Add(rooturl + "smtpserver", om.Connection.Host);
        //    myMail.Fields.Add(rooturl + "smtpserverport", om.Connection.Port);
        //    myMail.Fields.Add(rooturl + "sendusing", "2");
        //    //sendusing: cdoSendUsingPort, value 2, for sending the message using
        //    //the network.

        //    //smtpauthenticate: Specifies the mechanism used when authenticating to an SMTP
        //    //service over the network. Possible values are:
        //    //- cdoAnonymous, value 0. Do not authenticate.
        //    //- cdoBasic, value 1. Use basic clear-text authentication.
        //    //When using this option you have to provide the user name and password
        //    //through the sendusername and sendpassword fields.
        //    //- cdoNTLM, value 2. The current process security context is used to
        //    // authenticate with the service.
        //    myMail.Fields.Add(rooturl + "smtpauthenticate", "1"); //Use 0 for anonymous
        //    myMail.Fields.Add(rooturl + "sendusername", om.Connection.UserName);
        //    myMail.Fields.Add(rooturl + "sendpassword", om.Connection.Password);
        //    myMail.Fields.Add(rooturl + "smtpusessl", "true");
        //    myMail.From = om.SendAs;
        //    myMail.To = String.Join(";", om.SendTo.ToArray());
        //    myMail.Subject = om.Subject;
        //    myMail.BodyFormat = System.Web.Mail.MailFormat.Text;
        //    myMail.Body = om.Body;
        //    if (om.Attachments.Any())
        //    {
        //        foreach (string item in om.Attachments)
        //        {
        //            System.Web.Mail.MailAttachment attachment = new System.Web.Mail.MailAttachment(item);
        //            myMail.Attachments.Add(attachment);
        //        }
        //        myMail.Priority = System.Web.Mail.MailPriority.High;
        //    }

        //    System.Web.Mail.SmtpMail.SmtpServer = om.Connection.Host; // "smtp.gmail.com:465";
        //    System.Web.Mail.SmtpMail.Send(myMail);
        //}
        #endregion SendMessageImplicit    

        #region AddError private method

        private void AddError(Exception ex, string sindent = "")
        {
            this.ErrorList.Add(sindent + ex.Message);
            if (ex.Data != null)
            {
                if (ex.Data.Count > 0)
                {
                    foreach (DictionaryEntry de in ex.Data)
                    {
                        this.ErrorList.Add(string.Format(sindent + "The key is '{0}' and the value is: {1}", de.Key, de.Value));
                    }
                }
            }
            if (ex.InnerException != null) AddError(ex.InnerException, "-InnerException: ");
        }
        #endregion AddError private method

    }
}