using NLog;
using System;
using System.Linq;
using System.Threading;

namespace EgtBEAMWALL.DataLayer.Controllers
{
    public class DbController : IDisposable
    {
        #region Public Fields

        /// <summary>
        /// Singleton gestione
        /// </summary>
        public static DbController man = new DbController();

        #endregion Public Fields

        #region Public Constructors

        public DbController()
        {
            // Initialize database context for ADMIN
            adbCtx = new AdminContext(DbConfig.ADMIN_CONNECTION_STRING);
            // DB standard utente
            dbCtx = new AdminContext(DbConfig.CONNECTION_STRING);
        }

        #endregion Public Constructors

        #region Public Methods

        /// <summary>
        /// Verifica necessità creazione utente (locale o in rete)
        /// </summary>
        /// <param name="username"></param>
        /// <param name="pwd"></param>
        /// <param name="isNetwork"></param>
        /// <returns></returns>
        public bool checkCreateUser(string username, string pwd, bool isNetwork)
        {
            bool answ = false;
            string domain = isNetwork ? "%" : "localhost";
            answ = doCheckCreate(username, pwd, answ, domain, false);
            if (answ)
            {
                doGrantPriv(username, domain, "ALL ON *.*");
            }
            return answ;
        }

        /// <summary>
        /// Migrazione esplicita del DB da parte dell'admin controller
        /// </summary>
        /// <returns></returns>
        public bool checkMigrateDb()
        {
            return adbCtx.SetUpDbConnectionAndDbConfig();
        }

        public void Dispose()
        {
            // Clear database context
            adbCtx.Dispose();
        }

        /// <summary>
        /// Verifica esistenza views + utente relativo eventualmente ricreandole + fornendo accesso
        /// all'utente ReadOnly Essetre
        /// </summary>
        /// <param name="dbName"></param>
        /// <param name="username"></param>
        /// <param name="pwd"></param>
        /// <returns></returns>
        public bool refreshViews(string dbName, string username = "S3_User", string pwd = "S3_P4ssw0rd")
        {
            bool answ = false;

            // in primis refresh delle view
            string sqlCommand = @"CREATE OR REPLACE VIEW v_expProdRaw AS
                                    SELECT DbId, AlarmDtEvent AS DtEvent, VarValue,
                                    SUBSTRING_INDEX(VarValue, ';', 1) AS Prod,
                                    SUBSTRING_INDEX(SUBSTRING_INDEX(VarValue, ';', 2), ';', -1) AS MachGroupId,
                                    SUBSTRING_INDEX(SUBSTRING_INDEX(VarValue, ';', -2), ';', 1) AS PartId,
                                    SUBSTRING_INDEX(VarValue, ';', -1) AS Status
                                    FROM logmachine
                                    WHERE ResultType = 1
                                    AND VarAddress = 1;

                                    CREATE OR REPLACE VIEW v_expProd AS
                                    SELECT vp.DtEvent, vp.Prod, mgl.Name AS mgName, pl.PDN, vp.`Status`
                                    FROM v_expProdRaw AS vp
                                    INNER JOIN MachGroupList mgl ON vp.MachGroupId = mgl.Id
                                    INNER JOIN partlist pl ON vp.PartId = pl.Id;";
            dbCtx.Database.ExecuteSqlCommand(System.Data.Entity.TransactionalBehavior.DoNotEnsureTransaction, sqlCommand);
            Thread.Sleep(100);

            // ora setup parametri x utente
            string domain = "%";

            // ora controllo utente con diritti
            answ = doCheckCreate(username, pwd, answ, domain, true);
            if (answ)
            {
                doGrantPriv(username, domain, "USAGE ON *.*");
                doGrantPriv(username, domain, $"SELECT ON {dbName}.v_expProd");
            }

            return answ;
        }

        /// <summary>
        /// ATTENZIONE!!!! Procedura di Reset del DB
        /// </summary>
        /// <returns></returns>
        public bool ResetDb()
        {
            bool answ = false;

            try
            {
                adbCtx
                    .Database
                    .SqlQuery<int>("CALL stp_ResetDb()");
                answ = true;

                adbCtx.SaveChanges();
            }
            catch (Exception exc)
            {
                string errMessage = $"EXCEPTION on DbController.ResetDb: {Environment.NewLine}{exc}";
                Console.WriteLine(errMessage);
                Log.Error(errMessage);
            }

            return answ;
        }

        #endregion Public Methods

        #region Private Fields

        private AdminContext adbCtx;
        private AdminContext dbCtx;

        /// <summary>
        /// Istanza logger
        /// </summary>
        private NLog.Logger Log = LogManager.GetCurrentClassLogger();

        #endregion Private Fields

        #region Private Methods

        /// <summary>
        /// Procedura interna effettiva di check utente ed eventuale creazione + grant level
        /// </summary>
        /// <param name="username"></param>
        /// <param name="pwd"></param>
        /// <param name="answ"></param>
        /// <param name="domain"></param>
        /// <param name="force">Force = esegue un drop prima...</param>
        /// <returns></returns>
        private bool doCheckCreate(string username, string pwd, bool answ, string domain, bool force)
        {
            // ricerca utente...
            var numUser = adbCtx
                .UserList
                .Where(x => x.User == username)
                .ToList()
                .Count;
            if (numUser > 0)
            {
                answ = true;
            }
            if (!answ)
            {
                // creo utente
                string sqlCommand = $"DROP USER  IF EXISTS {username};";
                if (force)
                {
                    adbCtx.Database.ExecuteSqlCommand(System.Data.Entity.TransactionalBehavior.DoNotEnsureTransaction, sqlCommand);
                    Thread.Sleep(100);
                }
                sqlCommand = "FLUSH PRIVILEGES;";
                adbCtx.Database.ExecuteSqlCommand(System.Data.Entity.TransactionalBehavior.DoNotEnsureTransaction, sqlCommand);
                Thread.Sleep(100);
                sqlCommand = $"CREATE USER '{username}'@'{domain}' IDENTIFIED BY '{pwd}';";
                adbCtx.Database.ExecuteSqlCommand(System.Data.Entity.TransactionalBehavior.DoNotEnsureTransaction, sqlCommand);
                Thread.Sleep(100);
                sqlCommand = "FLUSH PRIVILEGES;";
                adbCtx.Database.ExecuteSqlCommand(System.Data.Entity.TransactionalBehavior.DoNotEnsureTransaction, sqlCommand);
                Thread.Sleep(100);
                answ = true;
            }

            return answ;
        }

        /// <summary>
        /// Procedura interna effettiva di grant level x utente
        /// </summary>
        /// <param name="username"></param>
        /// <param name="domain"></param>
        /// <param name="grantLevel"></param>
        /// <returns></returns>
        private bool doGrantPriv(string username, string domain, string grantLevel)
        {
            bool answ = false;

            string sqlCommand = "FLUSH PRIVILEGES;";
            adbCtx.Database.ExecuteSqlCommand(System.Data.Entity.TransactionalBehavior.DoNotEnsureTransaction, sqlCommand);
            Thread.Sleep(100);
            sqlCommand = $"GRANT {grantLevel} TO '{username}'@'{domain}';";
            adbCtx.Database.ExecuteSqlCommand(System.Data.Entity.TransactionalBehavior.DoNotEnsureTransaction, sqlCommand);
            Thread.Sleep(100);
            sqlCommand = "FLUSH PRIVILEGES;";
            adbCtx.Database.ExecuteSqlCommand(System.Data.Entity.TransactionalBehavior.DoNotEnsureTransaction, sqlCommand);
            Thread.Sleep(100);
            answ = true;

            return answ;
        }

        #endregion Private Methods
    }
}