using GreenTree.Nachtragsmanagement.Core.Plugins;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using GreenTree.Nachtragsmanagement.Core.Domain.Misc;
using GreenTree.Nachtragsmanagement.Services.User;
using GreenTree.Nachtragsmanagement.Services.Configuration;
using GreenTree.Nachtragsmanagement.Services.Appendix;
using GreenTree.Nachtragsmanagement.Core.Domain.Appendix;
using System.Net.Mail;
using System.Net;
using System.Globalization;
using Quartz;
using GreenTree.Nachtragsmanagement.Services.Logging;

namespace GreenTree.Nachtragsmanagement.Web.Implementations
{
    public class AppendixNotificationPlugin : INotificationPlugin, IJob
    {
        #region Services

        private readonly IUserService _userService;
        private readonly IConfigurationService _configurationService;
        private readonly IAppendixService _appendixService;
        private readonly IServiceLogger _logger;

        #endregion

        #region Properties

        /// <summary>
        /// Id
        /// </summary>
        public Guid Id
        {
            get
            {
                return Guid.Parse("E99CA4A1-B3A9-4AA6-BBAD-9254CEED0A45");
            }
        }

        /// <summary>
        /// System name
        /// </summary>
        public string SystemName
        {
            get
            {
                return "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin";
            }
        }

        /// <summary>
        /// List of available notification jobs
        /// </summary>
        public List<NotificationJob> AvailableNotificationJobs
        {
            get
            {
                return new List<NotificationJob>
                {
                    new NotificationJob
                    (
                        Guid.Parse("2F3642E0-259D-466D-8629-CB279F740313"),
                        "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationDate",
                        "Verhandlungstermine überprüfen",
                        "Erstellt automatisch Benachrichtigungen für Nachträge, die nach 8 Wochen nach Einreichung noch keinen " +
                        "Verhandlungstermin gesetzt haben und ändert den entsprechenden Status ab"
                    ),
                    new NotificationJob
                    (
                        Guid.Parse("2E46B32A-1912-47F9-951E-C8188AA9BA50"),
                        "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationProtocol",
                        "Verhandlungsprotokolle überprüfen",
                        "Erstellt automatisch Benachrichtigungen für Nachträge, die nach 2 Wochen zwar verhandelt sind, " +
                        "jedoch noch kein Protokoll aufweisen."
                    ),
                    new NotificationJob
                    (
                        Guid.Parse("B39FB44A-6E57-458C-A403-2C4FA7581F15"),
                        "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationOrder",
                        "Bestellnummern überprüfen",
                        "Erstellt automatisch Benachrichtigungen für Nachträge, die nach 2 Wochen zwar verhandelt sind, " +
                        "jedoch noch keine Bestellnummer aufweisen."
                    )
                };
            }
        }

        /// <summary>
        /// Displayed name
        /// </summary>
        public string Name
        {
            get
            {
                return "Nachtragsbenachrichtigung";
            }
        }

        /// <summary>
        /// Further description on how this plugin works
        /// </summary>
        public string Description
        {
            get
            {
                return
                    "Erstellt automatisch Benachrichtigungen für Nachträge, die nach 8 Wochen nach Einreichung noch keinen " +
                    "Verhandlungstermin gesetzt haben und ändert den entsprechenden Status ab. Außerdem werden alle 2 Wochen " +
                    "Benachrichtigungen für Nachträge erstellt, die zwar verhandelt sind, jedoch noch kein Protokoll aufweisen oder " +
                    "keine Bestellnummer aufweisen.";
            }
        }
        #endregion

        /// <summary>
        /// Initializes a new instance of the AppendixNotificationPlugin class
        /// </summary>
        public AppendixNotificationPlugin() { }

        /// <summary>
        /// Initializes a new instance of the AppendixNotificationPlugin class
        /// </summary>
        public AppendixNotificationPlugin(
            IUserService userService,
            IConfigurationService configurationService,
            IAppendixService appendixService,
            IServiceLogger logger)
        {
            _userService = userService;
            _configurationService = configurationService;
            _appendixService = appendixService;
            _logger = logger;
        }

        #region Quartz implmentation

        /// <summary>
        /// Executes the current job
        /// </summary>
        /// <param name="context"></param>
        public void Execute(IJobExecutionContext context)
        {
            if (!context.JobDetail.JobDataMap.ContainsKey("MailNotifications"))
                return;

            var mailNotifications = context.JobDetail.JobDataMap.Get("MailNotifications") as IEnumerable<MailNotification>;

            ProcessNotifications(mailNotifications);
        }

        #endregion

        #region Processing

        /// <summary>
        /// Process all mail notifications registered for that plugin
        /// </summary>
        /// <param name="mailNotifications">The notifications which shall be generated.</param>
        public void ProcessNotifications(IEnumerable<MailNotification> mailNotifications)
        {
            if (mailNotifications == null || (mailNotifications != null && !mailNotifications.Any())) return;

            _logger.Information(
                String.Format(
                    "Starte Verarbeitung Mail-Benachrichtigung in \"{0}\" für \"{1} ...", 
                    SystemName, mailNotifications.First().NotificationJobSystemName));

            foreach (var notification in mailNotifications)
            {
                try
                {
                    switch (notification.NotificationJobSystemName)
                    {
                        case "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationDate":
                            {
                                ProcessNegotiationDateNotification(notification);
                            }
                            break;
                        case "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationProtocol":
                            {
                                ProcessNegotiationProtocolNotification(notification);
                            }
                            break;
                        case "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationOrder":
                            {
                                ProcessNegotiationOrderNotification(notification);
                            }
                            break;
                        default:
                            continue;
                    }
                }
                catch (Exception ex)
                {
                    _logger.Error(
                        String.Format(
                            "Fehler bei Mail-Benachrichtigung in \"{0}\" für \"{1}", SystemName, notification.NotificationJobSystemName), ex);

                    continue;
                }
            }

            _logger.Information(
                String.Format(
                    "Verarbeitung Mail-Benachrichtigung in \"{0}\" für \"{1} erfolgreich abgeschlossen!",
                    SystemName, mailNotifications.First().NotificationJobSystemName));
        }

        /// <summary>
        /// Sets the corresponding status for appendices which offering date is older than N weeks and notifies 
        /// the correspondig recipients
        /// </summary>
        /// <param name="mailNotification">The notification which shall be generated.</param>
        private void ProcessNegotiationDateNotification(MailNotification mailNotification)
        {
            var ageDays = _configurationService.TryGetConfigItemValue<int>(
                "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationDate.AgeDays", 56);

            var stateSetId = _configurationService.TryGetConfigItemValue<int[]>(
                "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationDate.StateSet", new[] { 13 })[0];

            var stateConditionIds = _configurationService.TryGetConfigItemValue<int[]>(
                "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationDate.StateCondition", new[] { 1, 5, 6, 12 });

            var interval = _configurationService.TryGetConfigItemValue<int>(
                "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationDate.Interval", 2);

            interval = interval == 0
                ? 1
                : interval;

            var finishStateId = _appendixService.GetFinishState().Id;

            var appendices = _appendixService.GetAllAppendices()
                .Where(a => a.OfferingDate.HasValue &&
                            (DateTime.Now - a.OfferingDate).Value.Days >= ageDays &&
                            a.StateId != stateSetId && a.StateId != finishStateId &&
                            stateConditionIds.Contains(a.StateId.Value) && a.NegotiationDate == null)
                .ToList();

            var currentCalendarWeek = GetCalendarWeek(DateTime.Now);

            appendices = appendices
                .Where(a => (currentCalendarWeek - GetCalendarWeek(a.OfferingDate.Value) % interval == 0) ||
                            (currentCalendarWeek - GetCalendarWeek(a.OfferingDate.Value) < 0))
                .ToList();

            if (appendices.Any())
            {
                var mailBody = GenerateNegotiationDateMailBody(appendices);

                foreach (var appendix in appendices)
                {
                    appendix.StateId = stateSetId;

                    _appendixService.UpdateAppendix(appendix);
                }

                SendNotification(mailNotification, "Autom. Übersicht nicht verhandelte Nachträge", mailBody);
            }
        }

        /// <summary>
        /// Sets the corresponding status for appendices which negotiation date is older than N weeks and notifies 
        /// the correspondig recipients when the protocol does not exist
        /// </summary>
        /// <param name="mailNotification">The notification which shall be generated.</param>
        private void ProcessNegotiationProtocolNotification(MailNotification mailNotification)
        {
            var ageDays = _configurationService.TryGetConfigItemValue<int>(
                "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationProtocol.AgeDays", 14);

            var interval = _configurationService.TryGetConfigItemValue<int>(
                "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationProtocol.Interval", 2);

            interval = interval == 0 
                ? 1 
                : interval;

            var finishStateId = _appendixService.GetFinishState().Id;

            var appendices = _appendixService.GetAllAppendices()
                .Where(a => a.NegotiationDate.HasValue &&
                            (DateTime.Now - a.NegotiationDate).Value.Days >= ageDays &&
                            !a.ProtocolExists && a.StateId != finishStateId)
                .ToList();

            var currentCalendarWeek = GetCalendarWeek(DateTime.Now);

            appendices = appendices
                .Where(a => (currentCalendarWeek - GetCalendarWeek(a.OfferingDate.Value) % interval == 0) ||
                            (currentCalendarWeek - GetCalendarWeek(a.OfferingDate.Value) < 0))
                .ToList();

            if (appendices.Any())
            {
                var mailBody = GenerateNegotiationProtocolMailBody(appendices);

                SendNotification(mailNotification, "Autom. Übersicht verhandelte Nachträge o. Protokoll", mailBody);
            }
        }

        /// <summary>
        /// Sets the corresponding status for appendices which negotiation date is older than N weeks and notifies 
        /// the correspondig recipients when the order number is still missing
        /// </summary>
        /// <param name="mailNotification">The notification which shall be generated.</param>
        private void ProcessNegotiationOrderNotification(MailNotification mailNotification)
        {
            var ageDays = _configurationService.TryGetConfigItemValue<int>(
                "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationOrder.AgeDays", 14);

            var interval = _configurationService.TryGetConfigItemValue<int>(
                "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationOrder.Interval", 2);

            interval = interval == 0
                ? 1
                : interval;

            var finishStateId = _appendixService.GetFinishState().Id;

            var appendices = _appendixService.GetAllAppendices()
                .Where(a => a.NegotiationDate.HasValue &&
                            (DateTime.Now - a.NegotiationDate).Value.Days >= ageDays &&
                            String.IsNullOrEmpty(a.OrderNumber) && a.StateId != finishStateId)
                .ToList();

            var currentCalendarWeek = GetCalendarWeek(DateTime.Now);

            appendices = appendices
                .Where(a => (currentCalendarWeek - GetCalendarWeek(a.OfferingDate.Value) % interval == 0) ||
                            (currentCalendarWeek - GetCalendarWeek(a.OfferingDate.Value) < 0))
                .ToList();

            if (appendices.Any())
            {
                var mailBody = GenerateNegotiationOrderMailBody(appendices);

                SendNotification(mailNotification, "Autom. Übersicht verhandelte Nachträge o. Bestellnummer", mailBody);
            }
        }

        #endregion

        #region Mail body generation

        /// <summary>
        /// Generates a mail body with a list of all appendices with a offering date later than N weeks
        /// </summary>
        /// <param name="appendices">Appendices matching that criteria.</param>
        public string GenerateNegotiationDateMailBody(IEnumerable<Appendix> appendices)
        {
            var template =
                "<html>" +
                "   <body>" +
                "       <h3>Übersicht \"Nicht verhandelte Nachträge\"</h3>" +
                "       <p>Folgende Nachträge haben ein Einreichdatum älter als 8 Wochen, jedoch noch kein Verhandlungstermin" +
                "       gesetzt:</p>" +
                "       <ul>" +
                "           {0}" +
                "       </ul>" +
                "   </body>" +
                "</html>";

            if (!appendices.Any()) return String.Format(template, String.Empty);

            var appendicesList = String.Empty;

            foreach (var appendix in appendices)
            {
                appendicesList += String.Format(
                    "<li>Nachtrag <b>\"{0}\"</b> in Baustelle <b>\"{1}\"</b> - Einreichdatum: <b>{2:dd.MM.yyyy}</b>",
                    appendix.CustomNumber, appendix.Site.CustomNumber, appendix.OfferingDate);
            }

            return String.Format(template, appendicesList);
        }

        /// <summary>
        /// Generates a mail body with a list of all appendices with a negotiation date later than N weeks and not existing protocol
        /// </summary>
        /// <param name="appendices">Appendices matching that criteria.</param>
        public string GenerateNegotiationProtocolMailBody(IEnumerable<Appendix> appendices)
        {
            var template =
                "<html>" +
                "   <body>" +
                "       <h3>Übersicht \"Verhandelte Nachträge ohne Protokoll\"</h3>" +
                "       <p>Folgende Nachträge haben ein Verhandlungstermin älter als zwei Wochen, jedoch noch kein Protokoll:" +
                "       <ul>" +
                "           {0}" +
                "       </ul>" +
                "   </body>" +
                "</html>";

            if (!appendices.Any()) return String.Format(template, String.Empty);

            var appendicesList = String.Empty;

            foreach (var appendix in appendices)
            {
                appendicesList += String.Format(
                    "<li>Nachtrag <b>\"{0}\"</b> in Baustelle <b>\"{1}\"</b> - Verhandlungsdatum: <b>{2:dd.MM.yyyy}</b>",
                    appendix.CustomNumber, 
                    appendix.Site == null
                        ? String.Empty
                        : appendix.Site.CustomNumber, 
                    appendix.NegotiationDate);
            }

            return String.Format(template, appendicesList);
        }

        /// <summary>
        /// Generates a mail body with a list of all appendices with a negotiation date later than N weeks and not existing order number
        /// </summary>
        /// <param name="appendices">Appendices matching that criteria.</param>
        public string GenerateNegotiationOrderMailBody(IEnumerable<Appendix> appendices)
        {
            var template =
                "<html>" +
                "   <body>" +
                "       <h3>Übersicht \"Verhandelte Nachträge ohne Bestellnummer\"</h3>" +
                "       <p>Folgende Nachträge haben ein Verhandlungstermin älter als zwei Wochen, jedoch noch keine Bestellnummer:" +
                "       <ul>" +
                "           {0}" +
                "       </ul>" +
                "   </body>" +
                "</html>";

            if (!appendices.Any()) return String.Format(template, String.Empty);

            var appendicesList = String.Empty;

            foreach (var appendix in appendices)
            {
                appendicesList += String.Format(
                    "<li>Nachtrag <b>\"{0}\"</b> in Baustelle <b>\"{1}\"</b> - Verhandlungsdatum: <b>{2:dd.MM.yyyy}</b>",
                    appendix.CustomNumber,
                    appendix.Site == null
                        ? String.Empty
                        : appendix.Site.CustomNumber,
                    appendix.NegotiationDate);
            }

            return String.Format(template, appendicesList);
        }

        #endregion

        #region Mail sending

        /// <summary>
        /// Sends a generated mail body to the specified recipients in the mail notification
        /// </summary>
        /// <param name="mailNotification">The mail notification.</param>
        /// <param name="subject">The mail subject.</param>
        /// <param name="body">The mail body.</param>
        public void SendNotification(MailNotification mailNotification, string subject, string body)
        {
            if (mailNotification == null) return;

            var mailServerConfig = _configurationService.GetCurrentConfiguration().MailServerElement;

            var smptClient = new SmtpClient(mailServerConfig.SmtpServer, mailServerConfig.Port)
            {
                EnableSsl = mailServerConfig.UseSsl,
                Credentials = new NetworkCredential(
                    mailServerConfig.Username,
                    mailServerConfig.Password,
                    mailServerConfig.Domain)
            };

            var recipients =
                mailNotification.Users
                    .Select(u => u.MailAddress);

            var mailMessage = new MailMessage
            {
                IsBodyHtml = true,
                Subject = subject,
                Body = body,
                From = new MailAddress("Nachtragsbenachrichtigung@schweerbau.de")
            };

            foreach (var recipient in recipients)
                mailMessage.To.Add(recipient);

            smptClient.Send(mailMessage);
        }

        #endregion

        #region Helper

        /// <summary>
        /// Determines the calendar week of a specific datetime
        /// </summary>
        /// <param name="date">The datetime which calendarweek should be calculated.</param>
        public static int GetCalendarWeek(DateTime date)
        {
            var currentCulture = CultureInfo.CurrentCulture;
            var calendar = currentCulture.Calendar;

            var calendarWeek = calendar.GetWeekOfYear(
                date, 
                currentCulture.DateTimeFormat.CalendarWeekRule, 
                currentCulture.DateTimeFormat.FirstDayOfWeek);

            if (calendarWeek > 52)
            {
                date = date.AddDays(7);
                var testCalendarWeek = calendar.GetWeekOfYear(date,
                   currentCulture.DateTimeFormat.CalendarWeekRule,
                   currentCulture.DateTimeFormat.FirstDayOfWeek);

                if (testCalendarWeek == 2)
                    calendarWeek = 1;
            }

            return calendarWeek;
        }

        #endregion
    }
}