using System;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using EnumsNET;
using AyaNova.Util;
using AyaNova.Api.ControllerHelpers;
using AyaNova.Biz;
using AyaNova.Models;


namespace AyaNova.Biz
{


    internal class ImportAyaNova7Biz : BizObject, IJobObject
    {
        // private readonly AyContext ct;
        // public readonly long userId;
        // private readonly AuthorizationRoles userRoles;


        internal ImportAyaNova7Biz(AyContext dbcontext, long currentUserId, AuthorizationRoles userRoles)
        {
            ct = dbcontext;
            UserId = currentUserId;
            CurrentUserRoles = userRoles;
            BizType = AyaType.AyaNova7Import;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////
        //JOB / OPERATIONS
        //
        public async Task HandleJobAsync(OpsJob job)
        {
            //Hand off the particular job to the corresponding processing code
            //NOTE: If this code throws an exception the caller (JobsBiz::ProcessJobsAsync) will automatically set the job to failed and log the exeption so 
            //basically any error condition during job processing should throw up an exception if it can't be handled

            //There might be future other job types so doing it like this for all biz job handlers for now
            switch (job.JobType)
            {
                case JobType.ImportV7Data:
                    await ProcessImportV7JobAsync(job);
                    break;
                default:
                    throw new System.ArgumentOutOfRangeException($"ImportAyaNovaBiz.HandleJob-> Invalid job type{job.JobType.ToString()}");
            }
        }


        /// <summary>
        /// /// Handle the test job
        /// </summary>
        /// <param name="job"></param>
        private async Task ProcessImportV7JobAsync(OpsJob job)
        {
            //NOTE: If this code throws an exception the caller will automatically set the job to failed and log the exeption so 
            //basically any error condition during job processing should throw up an exception if it can't be handled
            List<ImportAyaNova7MapItem> importMap = new List<ImportAyaNova7MapItem>();

            await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Running, ct);
            await JobsBiz.LogJobAsync(job.GId, $"ImportAyaNova7 starting", ct);

            //Get the import filename from the jsondata
            JObject jobData = JObject.Parse(job.JobInfo);
            var importFileName = jobData["ImportFileName"].Value<string>();
            if (string.IsNullOrWhiteSpace(importFileName))
            {
                throw new System.ArgumentNullException("ImportAyaNova7 job failed due to no import filename being specified");
            }

            if (!FileUtil.UtilityFileExists(importFileName))
            {
                throw new System.ArgumentNullException("ImportAyaNova7 job failed due to import file specified not existing");
            }


            //Erase all the data except for the license, schema and the manager user
            Microsoft.Extensions.Logging.ILogger log = AyaNova.Util.ApplicationLogging.CreateLogger("ImportAyaNova7");
            await JobsBiz.LogJobAsync(job.GId, "ImportAyaNova7 - Erasing data from database", ct);
            await DbUtil.EmptyBizDataFromDatabaseForSeedingOrImportingAsync(log);


            //get the contents of the archive
            List<string> zipEntries = FileUtil.ZipGetUtilityFileEntries(importFileName);

            //Iterate through the import items in the preferred order, checking for corresponding entries in the zip file 
            //In turn try to instantiate the type and id job that can handle that import, attempt to case or see if implements IImportAyaNova7Object
            //, if null / not supported for import then skip and log as not currently supported

            //Pass off the JSON data from the import file into the import job item by item

            //NOTE: Many of these require a second pass - one to get the object imported and then another to set another imported object to that object

            ////////////////////////////////////
            //TAGS 
            //

            //FIRST: import items that will become tags first into temporary cache lists
            Dictionary<string, Dictionary<Guid, string>> TagLists = new Dictionary<string, Dictionary<Guid, string>>();

            //IMPORT UNIT MODEL CATEGORIES AS TAGS 
            TagLists.Add("UnitModelCategory", new Dictionary<Guid, string>());
            await ImportTagListAsync("GZTW.AyaNova.BLL.UnitModelCategory", job.GId, TagLists["UnitModelCategory"], importFileName, zipEntries);

            //IMPORT Unit service type AS TAGS           
            TagLists.Add("UnitServiceType", new Dictionary<Guid, string>());
            await ImportTagListAsync("GZTW.AyaNova.BLL.UnitServiceType", job.GId, TagLists["UnitServiceType"], importFileName, zipEntries);

            //IMPORT Workorder Item Type AS TAGS   
            TagLists.Add("WorkorderItemType", new Dictionary<Guid, string>());
            await ImportTagListAsync("GZTW.AyaNova.BLL.WorkorderItemType", job.GId, TagLists["WorkorderItemType"], importFileName, zipEntries);

            //IMPORT Client group AS TAGS 
            TagLists.Add("ClientGroup", new Dictionary<Guid, string>());
            await ImportTagListAsync("GZTW.AyaNova.BLL.ClientGroup", job.GId, TagLists["ClientGroup"], importFileName, zipEntries);

            //IMPORT Workorder category AS TAGS 
            TagLists.Add("WorkorderCategory", new Dictionary<Guid, string>());
            await ImportTagListAsync("GZTW.AyaNova.BLL.WorkorderCategory", job.GId, TagLists["WorkorderCategory"], importFileName, zipEntries);

            //IMPORT Part Category AS TAGS           
            TagLists.Add("PartCategory", new Dictionary<Guid, string>());
            await ImportTagListAsync("GZTW.AyaNova.BLL.PartCategory", job.GId, TagLists["PartCategory"], importFileName, zipEntries);

            //IMPORT Dispatch zones AS TAGS           
            TagLists.Add("DispatchZone", new Dictionary<Guid, string>());
            await ImportTagListAsync("GZTW.AyaNova.BLL.DispatchZone", job.GId, TagLists["DispatchZone"], importFileName, zipEntries);

            //IMPORT Scheduleable User Groups AS TAGS           
            TagLists.Add("ScheduleableUserGroup", new Dictionary<Guid, string>());
            await ImportTagListAsync("GZTW.AyaNova.BLL.ScheduleableUserGroup", job.GId, TagLists["ScheduleableUserGroup"], importFileName, zipEntries);

            //Now can set users to correct tag for scheduleable user group
            // await ImportTagList("GZTW.AyaNova.BLL.ScheduleableUserGroup", "scheduleableusergrouptags", AyaType.Tag, job.GId, importMap, importFileName, zipEntries);

            //IMPORT REGIONS AS TAGS           
            TagLists.Add("Region", new Dictionary<Guid, string>());
            await ImportTagListAsync("GZTW.AyaNova.BLL.Region", job.GId, TagLists["Region"], importFileName, zipEntries);



            ////////////////////////////////////
            // OBJECTS
            //

            //USERS
            await DoImportAsync("GZTW.AyaNova.BLL.User", "main", AyaType.User, job.GId, importMap, importFileName, zipEntries, TagLists);
            //Now can do event log entries
            await DoImportAsync("GZTW.AyaNova.BLL.User", "eventlog", AyaType.User, job.GId, importMap, importFileName, zipEntries, TagLists);



            //IMPORT LOCALES
            await DoImportAsync("GZTW.AyaNova.BLL.Locale", "main", AyaType.Locale, job.GId, importMap, importFileName, zipEntries, TagLists);
            //Now can do user locale settings properly
            await DoImportAsync("GZTW.AyaNova.BLL.User", "locale", AyaType.User, job.GId, importMap, importFileName, zipEntries, TagLists);

            //TODO: CLIENT
            //do import for client here
            //Now can do user client settings properly
            //await DoImport("GZTW.AyaNova.BLL.User","clientid", AyaType.User, job.GId, importMap, importFileName, zipEntries);

            //TODO: HEADOFFICE
            //do import for ho here
            //Now can do user ho settings properly
            //await DoImport("GZTW.AyaNova.BLL.User","headofficeid", AyaType.User, job.GId, importMap, importFileName, zipEntries);



            //----------------
            await JobsBiz.LogJobAsync(job.GId, "ImportAyaNova7 finished", ct);
            await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Completed, ct);


        }

        /// <summary>
        /// This method does the actual import by
        /// - Fetching the list of entries in the zip archive that match the passed in startsWtih (folder name in zip archive)
        /// - Instantiating the corresponding new biz object type to handle the import
        /// - Passing the json parsed to the biz object one at a time to do the import
        /// </summary>
        /// <param name="entryStartsWith"></param>
        /// <param name="importTask"></param>
        /// <param name="importerType"></param>
        /// <param name="jobId"></param>
        /// <param name="importMap"></param>
        /// <param name="importFileName"></param>
        /// <param name="zipEntries"></param>
        /// <param name="tagLists"></param>
        /// <returns></returns>
        private async Task DoImportAsync(string entryStartsWith, string importTask, AyaType importerType, Guid jobId,
        List<ImportAyaNova7MapItem> importMap, string importFileName, List<string> zipEntries, Dictionary<string, Dictionary<Guid, string>> tagLists)
        {
            var zipObjectList = zipEntries.Where(m => m.StartsWith(entryStartsWith)).ToList();
            long importCount = 0;
            long notImportCount = 0;
            if (zipObjectList.Count > 0)
            {
                if (importTask != "main")
                {
                    await JobsBiz.LogJobAsync(jobId, $"Starting import sub-task {importTask} of {entryStartsWith} objects", ct);
                }
                else
                {
                    await JobsBiz.LogJobAsync(jobId, $"Starting import of {entryStartsWith} objects", ct);
                }

                var jList = FileUtil.ZipGetUtilityArchiveEntriesAsJsonObjects(zipObjectList, importFileName);

                IImportAyaNova7Object o = (IImportAyaNova7Object)BizObjectFactory.GetBizObject(importerType, ct);

                foreach (JObject j in jList)
                {
                    bool bImportSucceeded = false;
                    //some new types can import multiple old types and it might matter which is which to the importer
                    //so tag it with the original type
                    //------
                    j.Add("V7_TYPE", JToken.FromObject(entryStartsWith));
                    j.Add("IMPORT_TASK", JToken.FromObject(importTask));

                    bImportSucceeded = await o.ImportV7Async(j, importMap, jobId, tagLists);
                    if (bImportSucceeded)
                        importCount++;
                    else
                        notImportCount++;
                }

                if (importCount > 0)
                {
                    if (importTask != "main")
                    {
                        await JobsBiz.LogJobAsync(jobId, $"Successfully ran import subtask {importTask} on {importCount.ToString()} of {zipObjectList.Count.ToString()} {entryStartsWith} objects", ct);
                    }
                    else
                    {
                        await JobsBiz.LogJobAsync(jobId, $"Successfully imported {importCount.ToString()} of {zipObjectList.Count.ToString()} {entryStartsWith} objects", ct);
                    }
                }

                if (notImportCount > 0)
                {
                    if (importTask != "main")
                    {
                        await JobsBiz.LogJobAsync(jobId, $"Failed to run import subtask {importTask} on {notImportCount.ToString()} of {zipObjectList.Count.ToString()} {entryStartsWith} objects", ct);
                    }
                    else
                    {
                        await JobsBiz.LogJobAsync(jobId, $"Did not import {notImportCount.ToString()} of {zipObjectList.Count.ToString()} {entryStartsWith} objects", ct);
                    }
                }
            }
        }

        //Other job handlers here...


        private async Task ImportTagListAsync(string entryStartsWith, Guid jobId, Dictionary<Guid, string> tagDictionary, string importFileName, List<string> zipEntries)
        {
            var zipObjectList = zipEntries.Where(m => m.StartsWith(entryStartsWith)).ToList();
            long importCount = 0;

            if (zipObjectList.Count > 0)
            {
                await JobsBiz.LogJobAsync(jobId, $"Starting import to TAGS of {entryStartsWith} objects", ct);
                var jList = FileUtil.ZipGetUtilityArchiveEntriesAsJsonObjects(zipObjectList, importFileName);

                foreach (JObject j in jList)
                {

                    //------- IMPORT OBJECTS NAME AND ID TO LIST ------
                    #region main import task
                    var NewTagName = j["Name"].Value<string>();

                    var ShortTypeName = string.Empty;
                    switch (entryStartsWith)
                    {
                        case "GZTW.AyaNova.BLL.Region":
                            ShortTypeName = "region";
                            break;
                        case "GZTW.AyaNova.BLL.UnitModelCategory":
                            ShortTypeName = "unitmodelcategory";
                            break;
                        case "GZTW.AyaNova.BLL.UnitServiceType":
                            ShortTypeName = "unitservicetype";
                            break;
                        case "GZTW.AyaNova.BLL.WorkorderItemType":
                            ShortTypeName = "workorderitemtype";
                            break;
                        case "GZTW.AyaNova.BLL.ClientGroup":
                            ShortTypeName = "clientgroup";
                            break;
                        case "GZTW.AyaNova.BLL.WorkorderCategory":
                            ShortTypeName = "workordercategory";
                            break;
                        case "GZTW.AyaNova.BLL.PartCategory":
                            ShortTypeName = "partcategory";
                            break;
                        case "GZTW.AyaNova.BLL.DispatchZone":
                            ShortTypeName = "dispatchzone";
                            break;
                        case "GZTW.AyaNova.BLL.ScheduleableUserGroup":
                            ShortTypeName = "scheduleableusergroup";
                            break;
                    }

                    NewTagName += "." + ShortTypeName;
                    var OldV7Id = new Guid(j["ID"].Value<string>());

                    //Ensure it follows the rules
                    NewTagName = TagUtil.NormalizeTag(NewTagName);

                    //Add to dictionary if not present
                    tagDictionary.Add(OldV7Id, NewTagName);

                    #endregion

                    //-----------------------------------------------
                    importCount++;

                }

                if (importCount > 0)
                {
                    await JobsBiz.LogJobAsync(jobId, $"Successfully imported as TAGS {importCount.ToString()} of {zipObjectList.Count.ToString()} {entryStartsWith} objects", ct);
                }
            }
        }





        //////////////////////////////////////////////////////////////////
        //UTILITIES
        internal static async Task LogEventCreatedModifiedEventsAsync(JObject j, List<ImportAyaNova7MapItem> importMap, AyaType ayaType, AyContext ct)
        {
            var V7Id = new Guid(j["ID"].Value<string>());
            var RavenId = importMap.Where(m => m.V7ObjectId == V7Id).First().NewObjectAyaTypeId.ObjectId;
            var Creator = importMap.Where(m => m.V7ObjectId == new Guid(j["Creator"].Value<string>())).First().NewObjectAyaTypeId.ObjectId;
            var Modifier = importMap.Where(m => m.V7ObjectId == new Guid(j["Modifier"].Value<string>())).First().NewObjectAyaTypeId.ObjectId;
            var Created = j["Created"].Value<DateTime>();
            var Modified = j["Modified"].Value<DateTime>();

            //handle EventLog entries for users now that we have the user's created                        
            //Created
            await EventLogProcessor.LogEventToDatabaseAsync(new Event(Creator, RavenId, ayaType, AyaEvent.Created, Created), ct);
            //MODIFIED
            await EventLogProcessor.LogEventToDatabaseAsync(new Event(Modifier, RavenId, ayaType, AyaEvent.Modified, Modified), ct);

        }

        /////////////////////////////////////////////////////////////////////

    }//eoc


}//eons

