using System;
using System.Collections.Generic;
using AyaNova.Models;
using AyaNova.Biz;
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;
using Bogus;
using AyaNova.Api.ControllerHelpers;
using System.Diagnostics;


namespace AyaNova.Util
{

    public static class Seeder
    {
        public enum SeedLevel { SmallOneManShopTrialDataSet, MediumLocalServiceCompanyTrialDataSet, LargeCorporateMultiRegionalTrialDataSet, HugeForLoadTest };


        //////////////////////////////////////////////////////
        //Seed database for trial and testing purposes
        // 

        public static void SeedDatabase(SeedLevel slevel, Decimal timeZoneOffset)
        {
            SeedDatabase(slevel, Guid.Empty, timeZoneOffset);
        }


        public static void SeedDatabase(SeedLevel slevel, Guid JobId, Decimal timeZoneOffset)
        {
            bool LogJob = JobId != Guid.Empty;


            ILogger log = AyaNova.Util.ApplicationLogging.CreateLogger("Seeder");
            ApiServerState apiServerState = (ApiServerState)ServiceProviderProvider.Provider.GetService(typeof(ApiServerState));

            //get the current server state so can set back to it later
            ApiServerState.ServerState wasServerState = apiServerState.GetState();
            string wasReason = apiServerState.Reason;

            //START SERIAL NUMBER GENERATORS
            if (ServerBootConfig.WIDGET_SERIAL == null)
                ServerBootConfig.WIDGET_SERIAL = new AutoId(0);

            try
            {
                LogStatus(JobId, LogJob, log, $"SEEDER: Seed data level - {slevel.ToString()}, timeZoneOffset - {timeZoneOffset.ToString()}");

                //Only allow this in a trial database
                if (!AyaNova.Core.License.ActiveKey.TrialLicense)
                {
                    var msg = "This database has a registered license key so it can't be seeded";
                    LogStatus(JobId, LogJob, log, msg);
                    throw new System.NotSupportedException(msg);
                }

                //validate timezone offset
                if (timeZoneOffset > 14 || timeZoneOffset < (-12))
                {
                    var msg = $"Time zone offset \"{timeZoneOffset.ToString()}\" is not valid";
                    LogStatus(JobId, LogJob, log, msg);
                    throw new System.NotSupportedException(msg);
                }

                apiServerState.SetOpsOnly("Seeding database");

                //Erase all the data except for the license, schema and the manager user
                DbUtil.PrepareDatabaseForSeeding(log);

                //Set the time zone of the manager account 
                {
                    var ct = ServiceProviderProvider.DBContext;
                    var mgr = ct.UserOptions.FirstAsync(m => m.Id == 1).Result;
                    mgr.TimeZoneOffset = timeZoneOffset;
                    ct.SaveChanges();
                }


                //Seed special test data for integration testing
                //log.LogInformation("Seeding known users");
                SeedKnownUsers(log, timeZoneOffset);

                //log.LogInformation("Seeding all other data");
                switch (slevel)
                {
                    case SeedLevel.SmallOneManShopTrialDataSet:
                        {
                            #region GenSmall
                            //This is for a busy but one man shop with a single office person handling stuff back at the shop

                            //Generate owner and lead tech
                            GenSeedUser(log, 1, AuthorizationRoles.BizAdminFull | AuthorizationRoles.DispatchFull | AuthorizationRoles.InventoryFull | AuthorizationRoles.OpsAdminFull, UserType.Schedulable, timeZoneOffset);

                            //Generate one office person / secretary
                            GenSeedUser(log, 1, AuthorizationRoles.DispatchFull | AuthorizationRoles.InventoryFull | AuthorizationRoles.AccountingFull, UserType.NonSchedulable, timeZoneOffset);

                            //100 widgets
                            GenSeedWidget(log, 100);
                            #endregion gensmall
                        }
                        break;
                    case SeedLevel.MediumLocalServiceCompanyTrialDataSet:
                        {
                            #region GenMedium
                            //This is for a typical AyaNova medium busy shop
                            //has one location, many techs and full staff for each department

                            //One IT administrator, can change ops but nothing else
                            GenSeedUser(log, 1, AuthorizationRoles.OpsAdminFull, UserType.NonSchedulable, timeZoneOffset);

                            //One business administrator, can view ops issues
                            GenSeedUser(log, 1, AuthorizationRoles.BizAdminFull | AuthorizationRoles.OpsAdminLimited, UserType.NonSchedulable, timeZoneOffset);

                            //One owner who doesn't control anything but views stuff
                            GenSeedUser(log, 1, AuthorizationRoles.DispatchLimited | AuthorizationRoles.InventoryLimited | AuthorizationRoles.OpsAdminLimited, UserType.NonSchedulable, timeZoneOffset);

                            //20 techs
                            GenSeedUser(log, 20, AuthorizationRoles.TechFull | AuthorizationRoles.DispatchLimited, UserType.Schedulable, timeZoneOffset);

                            //2 subcontractors
                            GenSeedUser(log, 2, AuthorizationRoles.SubContractorFull, UserType.Subcontractor, timeZoneOffset);

                            //3 sales / generic office people people
                            GenSeedUser(log, 3, AuthorizationRoles.DispatchLimited | AuthorizationRoles.InventoryLimited, UserType.NonSchedulable, timeZoneOffset);

                            //1 dispatch manager
                            GenSeedUser(log, 1, AuthorizationRoles.DispatchFull | AuthorizationRoles.InventoryLimited, UserType.NonSchedulable, timeZoneOffset);

                            //1 Inventory manager
                            GenSeedUser(log, 1, AuthorizationRoles.InventoryFull | AuthorizationRoles.DispatchLimited, UserType.NonSchedulable, timeZoneOffset);

                            //1 accountant / bookkeeper
                            GenSeedUser(log, 1, AuthorizationRoles.AccountingFull | AuthorizationRoles.BizAdminLimited, UserType.NonSchedulable, timeZoneOffset);

                            //10 full on client users
                            GenSeedUser(log, 10, AuthorizationRoles.ClientLimited, UserType.Client, timeZoneOffset);

                            //10 limited client users
                            GenSeedUser(log, 10, AuthorizationRoles.ClientLimited, UserType.Client, timeZoneOffset);

                            //500 widgets
                            GenSeedWidget(log, 500);
                            #endregion genmedium
                        }
                        break;
                    case SeedLevel.LargeCorporateMultiRegionalTrialDataSet:
                        {
                            #region GenLarge
                            //this is a large corporation with multiple branches in multiple locations all in the same country
                            //Each location has a full staff and corporate head office has an overarching staff member in charge of each location

                            //PERF                           
                            LogStatus(JobId, LogJob, log, $"Seeding 279 user(s)....");
                            var watch = new Stopwatch();
                            watch.Start();

                            //IT administrator, can change ops but nothing else
                            GenSeedUser(log, 2, AuthorizationRoles.OpsAdminFull, UserType.NonSchedulable, timeZoneOffset);

                            //business administrator, can view ops issues
                            GenSeedUser(log, 2, AuthorizationRoles.BizAdminFull | AuthorizationRoles.OpsAdminLimited, UserType.NonSchedulable, timeZoneOffset);

                            //owner / upper management who doesn't control anything but views stuff
                            GenSeedUser(log, 5, AuthorizationRoles.DispatchLimited | AuthorizationRoles.InventoryLimited | AuthorizationRoles.OpsAdminLimited, UserType.NonSchedulable, timeZoneOffset);

                            //100 techs
                            GenSeedUser(log, 100, AuthorizationRoles.TechFull | AuthorizationRoles.DispatchLimited, UserType.Schedulable, timeZoneOffset);

                            //limited techs
                            GenSeedUser(log, 50, AuthorizationRoles.TechLimited | AuthorizationRoles.DispatchLimited, UserType.Schedulable, timeZoneOffset);

                            //20 subcontractors
                            GenSeedUser(log, 20, AuthorizationRoles.SubContractorFull, UserType.Subcontractor, timeZoneOffset);

                            //10 limited subcontractors
                            GenSeedUser(log, 10, AuthorizationRoles.SubContractorLimited, UserType.Subcontractor, timeZoneOffset);

                            //30 sales / generic office people people
                            GenSeedUser(log, 30, AuthorizationRoles.DispatchLimited | AuthorizationRoles.InventoryLimited, UserType.NonSchedulable, timeZoneOffset);

                            //5 dispatch manager 
                            GenSeedUser(log, 5, AuthorizationRoles.DispatchFull | AuthorizationRoles.InventoryLimited, UserType.NonSchedulable, timeZoneOffset);

                            //5 Inventory manager
                            GenSeedUser(log, 5, AuthorizationRoles.InventoryFull | AuthorizationRoles.DispatchLimited, UserType.NonSchedulable, timeZoneOffset);

                            //10 Inventory manager assistants
                            GenSeedUser(log, 5, AuthorizationRoles.InventoryLimited, UserType.NonSchedulable, timeZoneOffset);

                            //5 accountant / bookkeeper
                            GenSeedUser(log, 5, AuthorizationRoles.AccountingFull | AuthorizationRoles.BizAdminLimited, UserType.NonSchedulable, timeZoneOffset);

                            //100 full on client users
                            GenSeedUser(log, 20, AuthorizationRoles.ClientFull, UserType.Client, timeZoneOffset);

                            //100 limited client users
                            GenSeedUser(log, 20, AuthorizationRoles.ClientLimited, UserType.Client, timeZoneOffset);

                            //PERF
                            watch.Stop();
                            LogStatus(JobId, LogJob, log, $"279 Users seeded in {watch.ElapsedMilliseconds} ms");

                            //5000 widgets
                            LogStatus(JobId, LogJob, log, $"Seeding 5,000 Widgets....");
                            watch = new Stopwatch();
                            watch.Start();
                            GenSeedWidget(log, 5000);

                            //PERF
                            watch.Stop();
                            LogStatus(JobId, LogJob, log, $"5k Widgets seeded in {watch.ElapsedMilliseconds} ms");
                            #endregion genlarge
                        }
                        break;

                    case SeedLevel.HugeForLoadTest:
                        {
                            #region GenHuge
                            //this is the HUGE dataset for load and other testing
                            //It is acceptable for this seeding to take many hours as it would normally be used rarely

                            //PERF
                            LogStatus(JobId, LogJob, log, $"Seeding 1,410 user(s)....");
                            var watch = new Stopwatch();
                            watch.Start();

                            //IT administrator, can change ops but nothing else
                            GenSeedUser(log, 10, AuthorizationRoles.OpsAdminFull, UserType.NonSchedulable, timeZoneOffset);

                            //business administrator, can view ops issues
                            GenSeedUser(log, 10, AuthorizationRoles.BizAdminFull | AuthorizationRoles.OpsAdminLimited, UserType.NonSchedulable, timeZoneOffset);

                            //owner / upper management who doesn't control anything but views stuff
                            GenSeedUser(log, 20, AuthorizationRoles.DispatchLimited | AuthorizationRoles.InventoryLimited | AuthorizationRoles.OpsAdminLimited, UserType.NonSchedulable, timeZoneOffset);

                            //regular techs                           
                            GenSeedUser(log, 500, AuthorizationRoles.TechFull | AuthorizationRoles.DispatchLimited, UserType.Schedulable, timeZoneOffset);

                            //limited techs                           
                            GenSeedUser(log, 200, AuthorizationRoles.TechLimited | AuthorizationRoles.DispatchLimited, UserType.Schedulable, timeZoneOffset);

                            //subcontractors
                            GenSeedUser(log, 80, AuthorizationRoles.SubContractorFull, UserType.Subcontractor, timeZoneOffset);

                            //limited subcontractors
                            GenSeedUser(log, 40, AuthorizationRoles.SubContractorLimited, UserType.Subcontractor, timeZoneOffset);

                            //sales / generic office people people
                            GenSeedUser(log, 200, AuthorizationRoles.DispatchLimited | AuthorizationRoles.InventoryLimited, UserType.NonSchedulable, timeZoneOffset);

                            //dispatch manager 
                            GenSeedUser(log, 20, AuthorizationRoles.DispatchFull | AuthorizationRoles.InventoryLimited, UserType.NonSchedulable, timeZoneOffset);

                            //Inventory manager
                            GenSeedUser(log, 40, AuthorizationRoles.InventoryFull | AuthorizationRoles.DispatchLimited, UserType.NonSchedulable, timeZoneOffset);

                            //Inventory manager assistants
                            GenSeedUser(log, 20, AuthorizationRoles.InventoryLimited, UserType.NonSchedulable, timeZoneOffset);

                            //accountant / bookkeeper
                            GenSeedUser(log, 20, AuthorizationRoles.AccountingFull | AuthorizationRoles.BizAdminLimited, UserType.NonSchedulable, timeZoneOffset);

                            //full on client users
                            GenSeedUser(log, 200, AuthorizationRoles.ClientFull, UserType.Client, timeZoneOffset);

                            //limited client users
                            GenSeedUser(log, 50, AuthorizationRoles.ClientLimited, UserType.Client, timeZoneOffset);

                            //PERF
                            watch.Stop();
                            LogStatus(JobId, LogJob, log, $"1,410 Users seeded in {watch.ElapsedMilliseconds} ms");

                            //20000 widgets
                            LogStatus(JobId, LogJob, log, $"Seeding 20,000 Widgets....");
                            watch = new Stopwatch();
                            watch.Start();
                            GenSeedWidget(log, 20000);
                            watch.Stop();
                            LogStatus(JobId, LogJob, log, $"20k Widgets seeded in {watch.ElapsedMilliseconds} ms");
                            #endregion genhuge
                        }
                        break;

                }
                LogStatus(JobId, LogJob, log, "Seeding completed successfully");
            }
            catch (Exception ex)
            {
                log.LogError(ex, "Seeder:SeedDatabase error during ops");
                if (LogJob)
                    JobsBiz.LogJob(JobId, $"Seeder:SeedDatabase error during ops\r\n{ex.Message}");
                throw ex;
            }
            finally
            {
                log.LogInformation($"Seeder: setting server state back to {wasServerState.ToString()}");
                apiServerState.SetState(wasServerState, wasReason);
            }
        }


        //Log the status and also job if it's run via job
        private static void LogStatus(Guid JobId, bool LogJob, ILogger log, string msg)
        {
            log.LogInformation(msg);
            if (LogJob)
                JobsBiz.LogJob(JobId, msg);
        }


        public static long RUNNING_COUNT = 0;
        public static string Uniquify(string s)
        {
            return s + " " + (++RUNNING_COUNT).ToString();
        }


        private static string[] TagSet = new[] { "red", "orange", "yellow", "green", "blue", "indigo", "violet", "brown", "black", "white", "silver", "gold" };

        private static List<string> RandomTags(Faker f)
        {

            var t = f.PickRandom(TagSet, f.Random.Int(1, TagSet.Length));
            return new List<string>(t);

        }
        //////////////////////////////////////////////////////
        //Seed test data for integration tests
        //
        public static void SeedKnownUsers(ILogger log, decimal timeZoneOffset)
        {
            try
            {

                //TEST USERS
                //one of each role type            
                GenSeedUser(log, 1, AuthorizationRoles.BizAdminLimited, UserType.NonSchedulable, timeZoneOffset, "BizAdminLimited", "BizAdminLimited");
                GenSeedUser(log, 1, AuthorizationRoles.BizAdminFull, UserType.NonSchedulable, timeZoneOffset, "BizAdminFull", "BizAdminFull");
                GenSeedUser(log, 1, AuthorizationRoles.DispatchLimited, UserType.NonSchedulable, timeZoneOffset, "DispatchLimited", "DispatchLimited");
                GenSeedUser(log, 1, AuthorizationRoles.DispatchFull, UserType.NonSchedulable, timeZoneOffset, "DispatchFull", "DispatchFull");
                GenSeedUser(log, 1, AuthorizationRoles.InventoryLimited, UserType.NonSchedulable, timeZoneOffset, "InventoryLimited", "InventoryLimited");
                GenSeedUser(log, 1, AuthorizationRoles.InventoryFull, UserType.NonSchedulable, timeZoneOffset, "InventoryFull", "InventoryFull");
                GenSeedUser(log, 1, AuthorizationRoles.AccountingFull, UserType.NonSchedulable, timeZoneOffset, "Accounting", "Accounting");
                GenSeedUser(log, 1, AuthorizationRoles.TechLimited, UserType.Schedulable, timeZoneOffset, "TechLimited", "TechLimited");
                GenSeedUser(log, 1, AuthorizationRoles.TechFull, UserType.Schedulable, timeZoneOffset, "TechFull", "TechFull");

                GenSeedUser(log, 1, AuthorizationRoles.SubContractorLimited, UserType.Subcontractor, timeZoneOffset, "SubContractorLimited", "SubContractorLimited");
                GenSeedUser(log, 1, AuthorizationRoles.SubContractorFull, UserType.Subcontractor, timeZoneOffset, "SubContractorFull", "SubContractorFull");
                GenSeedUser(log, 1, AuthorizationRoles.ClientLimited, UserType.Client, timeZoneOffset, "ClientLimited", "ClientLimited");
                GenSeedUser(log, 1, AuthorizationRoles.ClientFull, UserType.Client, timeZoneOffset, "ClientFull", "ClientFull");

                GenSeedUser(log, 1, AuthorizationRoles.OpsAdminLimited, UserType.NonSchedulable, timeZoneOffset, "OpsAdminLimited", "OpsAdminLimited");
                GenSeedUser(log, 1, AuthorizationRoles.OpsAdminFull, UserType.NonSchedulable, timeZoneOffset, "OpsAdminFull", "OpsAdminFull");

                //PRIVACY TEST USER - this is used for a test to see if user info leaks into the logs
                GenSeedUser(log, 1, AuthorizationRoles.OpsAdminLimited, UserType.NonSchedulable, timeZoneOffset, "TEST_PRIVACY_USER_ACCOUNT", "TEST_PRIVACY_USER_ACCOUNT");

                //TEST NOT ACTIVE - this is used for a test to see if inactive user can login
                GenSeedUser(log, 1, AuthorizationRoles.OpsAdminLimited, UserType.NonSchedulable, timeZoneOffset, false, "TEST_INACTIVE", "TEST_INACTIVE");

                //Alternate locale users for each stock locale
                GenSeedUser(log, 1, AuthorizationRoles.AnyRole, UserType.Administrator, timeZoneOffset, true, "de", "de", LocaleBiz.LocaleNameToIdStatic("de"));
                GenSeedUser(log, 1, AuthorizationRoles.AnyRole, UserType.Administrator, timeZoneOffset, true, "es", "es", LocaleBiz.LocaleNameToIdStatic("es"));
                GenSeedUser(log, 1, AuthorizationRoles.AnyRole, UserType.Administrator, timeZoneOffset, true, "fr", "fr", LocaleBiz.LocaleNameToIdStatic("fr"));


            }
            catch
            {
                throw;
            }
        }



        /// <summary>
        /// Generate seed user with active=true
        /// (override to save typing)       
        /// </summary>
        /// <param name="log"></param>
        /// <param name="count"></param>
        /// <param name="roles"></param>
        /// <param name="userType"></param>
        /// <param name="timeZoneOffset"></param>
        /// <param name="login"></param>
        /// <param name="password"></param>
        public static void GenSeedUser(ILogger log, int count, AuthorizationRoles roles, UserType userType, decimal timeZoneOffset, string login, string password)
        {
            try
            {
                GenSeedUser(log, count, roles, userType, timeZoneOffset, true, login, password);
            }
            catch
            {
                throw;
            }
        }




        public static void GenSeedUser(ILogger log, int count, AuthorizationRoles roles, UserType userType, decimal timeZoneOffset, bool active = true, string login = null, string password = null, long localeId = 0)
        {

            UserBiz Biz = UserBiz.GetBizInternal(ServiceProviderProvider.DBContext);

            //allow creation of not entirely ready users (missing client id or subcontractor vendor id etc)
            Biz.SeedOrImportRelaxedRulesMode = true;

            Faker Fake = new Faker();

            for (int x = 0; x < count; x++)
            {
                User u = new User();
                u.Active = active;
                u.OwnerId = 1;
                var p = new Bogus.Person();
                u.Name = Uniquify(p.FullName);
                if (login != null)
                {
                    u.Login = login;
                    u.Name += " - " + login;
                }
                else
                    u.Login = p.FirstName;

                if (password != null)
                    u.Password = password;
                else
                    u.Password = u.Login;
                u.Roles = roles;
                u.LocaleId = localeId == 0 ? ServerBootConfig.AYANOVA_DEFAULT_LANGUAGE_ID : localeId;
                u.UserType = userType;
                //TODO: After have USER and HEADOFFICE and VENDOR, if usertype is subcontractor or client or headoffice it needs to set a corresponding user's parent org record id to go with it
                u.Tags = RandomTags(Fake);
                //Children and relations
                u.UserOptions = new UserOptions(1);
                u.UserOptions.EmailAddress = p.Email.Replace("gmail.com", "helloayanova.com").Replace("hotmail.com", "helloayanova.com").Replace("yahoo.com", "helloayanova.com");
                u.UserOptions.TimeZoneOffset = timeZoneOffset;

                var NewObject = Biz.Create(ServiceProviderProvider.DBContext, u);
                if (NewObject == null)
                {
                    log.LogError($"Seeder::GenSeedUser error creating user {u.Name}\r\n" + Biz.GetErrorsAsString());
                    throw new System.Exception("Seeder::GenSeedUser error creating user\r\n" + Biz.GetErrorsAsString());
                }
            }
        }

        //////////////////////////////////////////////////////
        //Seed widget for testing
        //
        public static void GenSeedWidget(ILogger log, int count)
        {
            WidgetBiz Biz = WidgetBiz.GetBizInternal(ServiceProviderProvider.DBContext);
            var f = new Bogus.Faker();
            for (int x = 0; x < count; x++)
            {
                Widget o = new Widget();
                o.Name = Uniquify(f.Commerce.ProductName());
                o.Active = true;
                o.StartDate = f.Date.Between(DateTime.Now, DateTime.Now.AddMinutes(60));
                o.EndDate = f.Date.Between(DateTime.Now.AddMinutes(90), DateTime.Now.AddHours(5));
                o.DollarAmount = Convert.ToDecimal(f.Commerce.Price());
                o.OwnerId = 1;
                //this is nonsense but just to test an enum
                o.Roles = AuthorizationRoles.DispatchLimited | AuthorizationRoles.InventoryLimited | AuthorizationRoles.OpsAdminLimited;
                o.Notes = f.Lorem.Paragraph();
                o.Tags = RandomTags(f);
                var NewObject = Biz.Create(ServiceProviderProvider.DBContext, o);
                if (NewObject == null)
                {
                    log.LogError($"Seeder::GenSeedWidget error creating widget {o.Name}\r\n" + Biz.GetErrorsAsString());
                    throw new System.Exception("Seeder::GenSeedWidget error creating widget\r\n" + Biz.GetErrorsAsString());
                }
            }
        }

    }//eoc



}//eons