using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

using VRH.Common;
using VRH.Log4Pro.MultiLanguageManager;
using Vrh.XmlProcessing;

namespace Vrh.iScheduler
{
    /// <summary>
    /// Az időzített feladat végrehajtása, ezen osztályon keresztül valósul meg.
    /// </summary>
    public class ScheduleExecute : IDisposable
    {
        private const string ERR_ONLYPLUGIN = "The '{0}' method can only be used in plugin mode!";

        #region Properties

        /// <summary>
        /// Az ütemezés adatai az Xml feldolgozás végeredménye.
        /// </summary>
        public SchedulerXmlParser Scheduler { get; set; }

        /// <summary>
        /// Egy webkliens, melyen keresztűl az akciók hívása történik.
        /// A konstruktorban jön létre, ott megtörténik a bejelentkezés.
        /// </summary>
        public CookieWebClient CWebClient { get; set; } = null;

        /// <summary>
        /// Plugin elérési mód esetén itt található a plugin.
        /// </summary>
        public Interfaces.ISchedulerPlugin Plugin { get; set; } = null;

        /// <summary>
        /// Az objektum műveletét meghívó URL.
        /// </summary>
        public string ExecuteUrl { get; set; }

		#endregion Properties

		#region Constructor 
		/// <summary>
		/// Az ütemezett feladatok végrehajtásának konstruktora.
		/// Itt megtörténik a paraméterező xml fájl ellenőrzése, annak feldolgozása.
		/// Amennyiben WebAppAction elérés van, akkor a CookieWebClient létrehozása a login végrehajtásával együtt.
		/// </summary>
		/// <param name="xmlConnectionString">iScheduler xml paraméterező fájl fizikai elérési útvonalával együtt.</param>
		/// <param name="baseFolder">Az alkalmazás alap mappája. Ebben van az App_Data. De ha nincs relatív útvonal, akkor akár üres is lehet.</param>
		/// <param name="lcid">A környezetben érvényes nyelvi kód. Ha null, vagy üres, akkor az XML paraméterező fájlban beállított lesz az érvényes.</param>
		public ScheduleExecute(string xmlConnectionString, string baseFolder, string lcid = null)
        {
            string dpx = "ScheduleExecute.Constructor: ";  // debug prefix
            System.Diagnostics.Debug.WriteLine($"{dpx} START: xmlConnectionString='{xmlConnectionString}'");

            #region Xml parser
            if (string.IsNullOrWhiteSpace(xmlConnectionString)) { xmlConnectionString = $"config={SchConst.DEFAULT_XMLCONFIGNAME};"; }
            var xmlc = new XmlConnection(xmlConnectionString);
            this.Scheduler = new SchedulerXmlParser(xmlc, baseFolder, lcid, null);
            if (this.Scheduler.ErrorList.Any())
            {
                throw new ApplicationException(string.Join(", ", this.Scheduler.ErrorList));
            }
            System.Diagnostics.Debug.WriteLine($"{dpx} xml parser OK");
			#endregion Xml parser

			if (this.Scheduler.AccessMode == AccessMode.Plugin)
			{   // ha plugin az elérési mód
                if (this.Scheduler.IsEmbededObjectType)
                {
                    switch (this.Scheduler.EmbededObjectType)
                    {
                        case EmbededObjectTypes.iSchedulerReport:
                            this.Plugin = new Vrh.iScheduler.Report.SchedulerPlugin();
                            break;
                        default:
                            break;
                    }
                }
                else
                {
                    Assembly asm = Assembly.LoadFile(this.Scheduler.Plugin.AssemblyPath);
                    Type type = asm.GetExportedTypes().FirstOrDefault(x => typeof(Interfaces.ISchedulerPlugin).IsAssignableFrom(x));
                    if (type != null)
                    {
                        this.Plugin = (Interfaces.ISchedulerPlugin)Activator.CreateInstance(type);
                        System.Diagnostics.Debug.WriteLine($"{dpx}The plugin load OK! TypeName = {this.Plugin.GetType().FullName}");
                    }
                    else
                    {
                        throw new ApplicationException($"The implementation of the 'ISchedulerPlugin' interface was not found in the assembly (DLL)! Assembly='{this.Scheduler.Plugin.AssemblyPath}'");
                    }
                }
            }
			else
			{   // controller akciók   
				#region CWebClient create and login
				Uri logUri = new Uri(this.Scheduler.WebAppAction.LoginUrl.GetUrl());
				CWebClient = new CookieWebClient(logUri, this.Scheduler.WebAppAction.UserName, this.Scheduler.WebAppAction.Password);
				System.Diagnostics.Debug.WriteLine($"{dpx} CookieWebClient create and login OK");
				#endregion CWebClient create and login

				#region Set ExecuteUrl
				if (String.IsNullOrWhiteSpace(this.Scheduler.Execute.Protocol)) this.Scheduler.Execute.Protocol = this.Scheduler.WebAppAction.LoginUrl.Protocol;
				if (String.IsNullOrWhiteSpace(this.Scheduler.Execute.HostName)) this.Scheduler.Execute.HostName = this.Scheduler.WebAppAction.LoginUrl.HostName;
				if (String.IsNullOrWhiteSpace(this.Scheduler.Execute.AppName)) this.Scheduler.Execute.AppName = this.Scheduler.WebAppAction.LoginUrl.AppName;
				this.ExecuteUrl = this.Scheduler.Execute.GetUrl();
				#endregion Set ExecuteUrl

				#region Set timout
				if (this.Scheduler.WebAppAction.ResponseTimeout > 0) this.CWebClient.Timeout = this.Scheduler.WebAppAction.ResponseTimeout;
				System.Diagnostics.Debug.WriteLine($"{dpx} END: ResponseTimeout={this.Scheduler.WebAppAction.ResponseTimeout} Timeout={this.CWebClient.Timeout}");
				#endregion Set timout
			}
        }
        #endregion Constructor 

        #region Run public method
        /// <summary>
        /// A paraméterben megadott ütemezés végrehajtása.
        /// Megkeresi, futtatja, az eredményt bejegyzi az adatbázisba.
        /// Akkor is bejegyzi, ha sikeres.
        /// </summary>
        /// <param name="scheduleid">Az időzítés egyedi azonosítója.</param>
        /// <param name="isOnlyTest">Ha igaz, akkor a futtatás eredményétől függetlenül aktív marad az időzítés.</param>
        public void Run(int scheduleid, bool isOnlyTest = false)
        {
            try
            {
                string dpx = "ScheduleExecute.Run: ";  // debug prefix
                DateTime started = DateTime.Now;
                System.Diagnostics.Debug.WriteLine($"\n{dpx}START scheduleid={scheduleid}, now={started}, connstr={this.Scheduler.ConnectionString}");
                Stopwatch runTime = new Stopwatch();
                runTime.Start();

                var loglist = new List<string> { $"*** Logs of {dpx}" };
                System.Diagnostics.Debug.WriteLine($"{dpx}{loglist.Last()}");

                using (var dbc = new SchedulerDB(this.Scheduler.ConnectionString))
                {
                    loglist.Add("Context inicialization OK");
                    System.Diagnostics.Debug.WriteLine($"{dpx}{loglist.Last()}");

                    #region Find the schedule
                    Schedule sch = dbc.Schedules.FirstOrDefault(x => x.Id == scheduleid);
                    if (sch == null)
                    {
                        string frm = MultiLanguageManager.GetTranslation(typeof(SchedulerWordCodes.iScheduler.Error.NotFoundSchedule));
                        throw new ApplicationException(String.Format(frm, scheduleid));
                    }
                    loglist.Add("Schedule found");
                    System.Diagnostics.Debug.WriteLine($"{dpx}{loglist.Last()}");
                    #endregion Find the schedule

                    var rijson = new ReturnInfoJSON();

					if (this.Scheduler.AccessMode == AccessMode.Plugin)
                    {
                        #region Plugin elérési mód esetén
                        try
                        {
                            rijson = this.Plugin.ExecuteScheduledTask(
                                this.Scheduler.Plugin.ObjectXml,
                                this.Scheduler.LCID,
								sch.ScheduleObject.ObjectId,
                                sch.ScheduleOperation.OperationId,
                                $"{sch.OperationTime:yyyyMMddHHmmss}"
                            );
                        }
                        catch (Exception ex)
                        {
                            rijson.ReturnValue = -77;
                            rijson.ReturnMessage = String.Join(VrhCommon.HTML_NEWLINE, VrhCommon.ErrorListBuilder(ex));
                            #region DEBUG
#if DEBUG
                            rijson.ReturnMessage += String.Concat(
                                VrhCommon.HTML_NEWLINE,
                                String.Join(VrhCommon.HTML_NEWLINE, this.Plugin.LogEntries)
                            );
#endif
                            #endregion DEBUG
                        }
                        #endregion Plugin elérési mód esetén
                    }
                    else
                    {
                        #region WebAppController elérési mód esetén

                        #region Compilation of execute URL 
                        string execUrl = this.ExecuteUrl;
                        this.Scheduler.UrlVars[SchConst.UrlVars.OPERATION] = sch.ScheduleOperation.OperationName;
                        this.Scheduler.UrlVars[SchConst.UrlVars.GROUPID] = sch.ScheduleObject.ObjectGroupId;
                        this.Scheduler.UrlVars[SchConst.UrlVars.OBJECTID] = sch.ScheduleObject.ObjectId;
                        this.Scheduler.UrlVars[SchConst.UrlVars.TIMESTAMP] = $"{sch.OperationTime:yyyyMMddHHmmss}";
                        execUrl = this.Scheduler.UrlVars.Substitution(execUrl);
                        loglist.Add($"executeUrl={execUrl}");
                        System.Diagnostics.Debug.WriteLine($"{dpx}{loglist.Last()}");
                        #endregion Compilation of execute URL 

                        try
                        {
                            byte[] ebyte = CWebClient.DownloadData(execUrl);
                            loglist.Add($"DownloadData OK");
                            System.Diagnostics.Debug.WriteLine($"{dpx}{loglist.Last()}");

                            string eresp = Encoding.UTF8.GetString(ebyte);
                            ReturnInfoJSON ri = JsonConvert.DeserializeObject<ReturnInfoJSON>(eresp);
                            loglist.Add($"JsonConvert.DeserializeObject OK");
                            System.Diagnostics.Debug.WriteLine($"{dpx}{loglist.Last()}");

                            rijson.ReturnValue = ri.ReturnValue;
                            rijson.ReturnMessage = ri.ReturnMessage;
                        }
                        catch (Exception ex)
                        {
                            List<string> elist = VrhCommon.ErrorListBuilder(CWebClient.LastException ?? ex);
                            elist.Insert(0, "ScheduleExecute.Run unsuccess!");
                            foreach (var item in loglist) elist.Add(item);  //naplózás
                            rijson.ReturnValue = -5;
                            rijson.ReturnMessage = String.Join("<br />", elist);
                        }
                        #endregion WebAppController elérési mód esetén
                    }

                    runTime.Stop();
                    if (!isOnlyTest)
                    {   // ha nem teszt céljából futott, akkor változtassuk meg az állapotát
                        sch.State = rijson.ReturnValue == 0 ? ScheduleStates.Success : ScheduleStates.Failed;
                    }
                    sch.ReturnValue = rijson.ReturnValue;
                    sch.ReturnMessage = String.Concat(rijson.ReturnMessage,
                        "<div style=\"font-size:smaller;border-top: 1px solid gray;\">Started: ",
                        started.ToString("hh:mm:ss"),
                        ", Operation time: ",
                        (runTime.ElapsedMilliseconds / 1000).ToString(),
                        "s</div>");
                    dbc.SaveChanges();
                }//using (iSchedulerDB dbc = new iSchedulerDB(constr)) VÉGE
            }
            catch (Exception ex)
            {
                throw new ApplicationException("Error in ScheduleExecute.Run!", ex);
            }
        }
        #endregion Run public method

        #region Check public method
        /// <summary>
        /// Az <paramref name="objectList"/>-ben megadott objektumok létezésének ellenőrzése.
        /// </summary>
        /// <param name="objectList">Az ellenőrzendő objektumok nevei vesszővel elválasztva.</param>
        /// <param name="groupId">
        /// Az objektumban alkalmazott csoport azonosító. Ha üres vagy null, akkor az objektum xml
        /// paraméterfájlban megadott lesz érvényes.
        /// </param>
        /// <returns>
        /// Egy <c>CheckListJSON</c> elemekből álló lista.
        /// Ha nincs elem, akkor üres lista lesz az eredmény, nem null.
        /// </returns>
        /// <exception cref="System.ApplicationException">Ha nem 'Plugin' mód esetén hívják meg.</exception>
        public List<CheckListJSON> Check(string objectList, string groupId = null)
        {
            #region DEBUG
#if DEBUG
            string thisfn = $"{nameof(ScheduleExecute)}.{nameof(List)}: ";  // debug prefix
            DateTime started = DateTime.Now;
            System.Diagnostics.Debug.WriteLine($"\n{thisfn}START xml={this.Scheduler.Plugin.ObjectXml}, objectList={objectList}, groupId={groupId}");
            Stopwatch runTime = new Stopwatch();
            runTime.Start();
#endif
            #endregion DEBUG

            if (this.Scheduler.AccessMode != AccessMode.Plugin)
            {
                throw new ApplicationException(string.Format(ERR_ONLYPLUGIN, nameof(Check)));
            }
            var checkObjects = this.Plugin.CheckObjects(this.Scheduler.Plugin.ObjectXml, objectList, groupId);

            #region DEBUG
#if DEBUG
            runTime.Stop();
            Debug.WriteLine($"\n{thisfn}  Operation time: {runTime.ElapsedMilliseconds / 1000}s");
#endif
            #endregion DEBUG

            return checkObjects;
        }
        #endregion Check public method

        #region List public method
        /// <summary>
        /// Ütemezhető objektumok listája.
        /// </summary>
        /// <param name="isActiveOnly">Csak az aktívnak jelölt elemek legyenek-e benne. Alapértelmezett értéke:true.</param>
        /// <param name="groupId">
        /// Az objektumban alkalmazott csoport azonosító. Ha üres vagy null, akkor az objektum xml
        /// paraméterfájlban megadott lesz érvényes.
        /// </param>
        /// <returns>
        /// Egy <c>SelectListJSON</c> elemekből álló lista.
        /// Ha nincs elem, akkor üres lista lesz az eredmény, nem null.
        /// </returns>
        /// <exception cref="System.ApplicationException">Ha nem 'Plugin' mód esetén hívják meg.</exception>
        public List<SelectListJSON> List(bool isActiveOnly = true, string groupId = null) 
        {
            #region DEBUG
#if DEBUG
            string thisfn = $"{nameof(ScheduleExecute)}.{nameof(List)}: ";  // debug prefix
            DateTime started = DateTime.Now;
            System.Diagnostics.Debug.WriteLine($"\n{thisfn}START xml={this.Scheduler.Plugin.ObjectXml}, isActiveOnly={isActiveOnly}, groupId={groupId}");
            Stopwatch runTime = new Stopwatch();
            runTime.Start();
#endif
            #endregion DEBUG

            if (this.Scheduler.AccessMode != AccessMode.Plugin) 
            {
                throw new ApplicationException(string.Format(ERR_ONLYPLUGIN, nameof(List)));
            }
            var listObjects = this.Plugin.ListObjects(this.Scheduler.Plugin.ObjectXml, isActiveOnly, groupId);

            #region DEBUG
#if DEBUG
            runTime.Stop();
            Debug.WriteLine($"\n{thisfn}  Operation time: {runTime.ElapsedMilliseconds / 1000}s");
#endif
            #endregion DEBUG

            return listObjects;
        }
        #endregion List public method

        #region ListOperations public method
        /// <summary>
        /// Ütemezhető objektumokon kezdeményezhető műveletek listája.
        /// </summary>
        /// <returns>
        /// Egy <c>SelectListJSON</c> elemekből álló lista.
        /// Ha nincs elem, akkor üres lista lesz az eredmény, nem null.
        /// </returns>
        public List<SelectListJSON> ListOperations()
        {
            #region DEBUG
#if DEBUG
            string thisfn = $"{nameof(ScheduleExecute)}.{nameof(ListOperations)}: ";  // debug prefix
            DateTime started = DateTime.Now;
            System.Diagnostics.Debug.WriteLine($"\n{thisfn}START");
            Stopwatch runTime = new Stopwatch();
            runTime.Start();
#endif
            #endregion DEBUG

            if (this.Scheduler.AccessMode != AccessMode.Plugin)
            {
                throw new ApplicationException(string.Format(ERR_ONLYPLUGIN, nameof(ListOperations)));
            }
            var listOps = this.Plugin.ListOperations();

            #region DEBUG
#if DEBUG
            runTime.Stop();
            Debug.WriteLine($"\n{thisfn}  Operation time: {runTime.ElapsedMilliseconds / 1000}s");
#endif
            #endregion DEBUG

            return listOps;
        }
        #endregion ListOperations public method

        #region IDisposable Support
        private bool disposedValue = false; // To detect redundant calls

        /// <summary>
        /// Dispose megvalósítása újrahívás elleni védelemmel.
        /// </summary>
        /// <param name="disposing">Ha igaz, akkor a dipose már megtörtént.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: dispose managed state (managed objects).
                    if (CWebClient != null)
                    {
                        CWebClient.Dispose();
                        this.Scheduler = null;
                    }
                }
                // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
                // TODO: set large fields to null.
                disposedValue = true;
            }
        }

        /// <summary>
        /// Dispose() megvalósítása.
        /// </summary>
        /// <remarks>This code added to correctly implement the disposable pattern.</remarks>
        public void Dispose()
        {
            // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
            Dispose(true);
            // TODO: uncomment the following line if the finalizer is overridden above.
            // GC.SuppressFinalize(this);
        }
        #endregion
    }
}
