using Ionic.Zip;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;

namespace EgtBEAMWALL.DataLayer
{
    public static class DbConfig
    {
        #region Public Fields

        public static string DATABASE_NAME = "EgtBwDb";

        public static int DATABASE_PROCESS_TIMEOUT = 5;
        public static string DATABASE_PWD = "viacremasca";

        // Database config
        public static string DATABASE_SERV = "127.0.0.1";

        public static string DATABASE_USER = "EgtUser";
        public static string ZIP_PWD = "viacremasca-viacremasca-viacremasca-viacremasca";

        #endregion Public Fields

        #region Public Properties

        /// <summary>
        /// DB Connection string per azioni amministrative
        /// </summary>
        public static string ADMIN_CONNECTION_STRING { get; set; } = "";

        /// <summary>
        /// DB Connection string, per effettuare migration riportare valore connessione admin cablato (server=localhost;port=3306;database=EgtBwDb_000102;)
        /// </summary>
        public static string CONNECTION_STRING { get; set; } = "";

        #endregion Public Properties

        #region Public Methods

        /// <summary>
        /// Effettua migrazione esplicita del DB
        /// </summary>
        /// <returns></returns>
        public static bool CheckMigrateDb()
        {
            // esecuzione script di migrations del DB
            return Controllers.DbController.man.checkMigrateDb();
        }

        /// <summary>
        /// Effettua verifica presenza utente
        /// </summary>
        /// <param name="nKey"></param>
        /// <param name="sKey"></param>
        /// <param name="isNetwork"></param>
        /// <returns></returns>
        public static bool CheckUser(string nKey, string sKey, bool isNetwork)
        {
            // esecuzione script di install locale
            return Controllers.DbController.man.checkCreateUser(DATABASE_USER, DATABASE_PWD, isNetwork);
        }

        /// <summary>
        /// Effettua verifica presenza view prod + relativo utente
        /// </summary>
        /// <param name="nKey"></param>
        /// <param name="sKey"></param>
        /// <param name="isNetwork"></param>
        /// <returns></returns>
        public static bool CheckViews(string nKey, string sKey)
        {
            // esecuzione script di install locale
            return Controllers.DbController.man.refreshViews(DATABASE_NAME);
        }

        /// <summary>
        /// Effettua DUMP del DB dato utente admin + percorso salvataggio (zip cifrato)
        /// </summary>
        /// <param name="zipFilePath">Percorso di salvataggio del dump (*.zip)</param>
        /// <param name="dbName">Nome del DB da processare (se vuoto quello di default calcolato)</param>
        /// <param name="mysqlDumpPath">Nome o Percorso Eseguibile mysqldump (opzionale)</param>
        /// <param name="exportOpt">Opzioni in fase di export (default: --skip-extended-insert)</param>
        /// <returns></returns>
        public static bool DataBaseDumpToFile(string zipFilePath, string dbName = "", string mysqlDumpPath = "mysqldump", string exportOpt = "--skip-extended-insert")
        {
            bool fatto = false;
            // fix path eseguibile
            if (string.IsNullOrEmpty(mysqlDumpPath))
            {
                mysqlDumpPath = "mysqldump";
            }
            if (string.IsNullOrEmpty(dbName))
            {
                dbName = DATABASE_NAME;
            }
            // verifica zip finale
            if (!zipFilePath.EndsWith(".zip"))
            {
                zipFilePath = $"{zipFilePath}.zip";
            }
            // creo nome file per export sql (temporaneo)
            string tempSqlPath = zipFilePath.Replace(".zip", ".sql");
            // esecuzione script x dump del DB
            string dirPath = Path.GetDirectoryName(zipFilePath);
            if (!Directory.Exists(dirPath))
            {
                Directory.CreateDirectory(dirPath);
            }
            // se ci fosse già file elimino (sql/zip)
            if (File.Exists(tempSqlPath))
            {
                File.Delete(tempSqlPath);
            }
            if (File.Exists(zipFilePath))
            {
                File.Delete(zipFilePath);
            }
            // creazione SQL, tramite  script esterno... importante parametro "--skip-extended-insert" altrimenti problemi con restore (anche se + verboso e lento in backup...)
            string callScript = $"\"{mysqlDumpPath}\" -u{DATABASE_USER}  -p{DATABASE_PWD} {dbName} {exportOpt} > {tempSqlPath}";
            ExecuteCommand(callScript);
            // compressione con pwd!
            using (ZipFile zip = new ZipFile())
            {
                // aggiungo pwd
                zip.Password = ZIP_PWD;
                // inserisco file
                zip.AddFile(tempSqlPath, "");
                zip.Save(zipFilePath);
                fatto = true;
            }
            // elimino file sql temporaneo...
            if (File.Exists(tempSqlPath))
            {
                File.Delete(tempSqlPath);
            }
            return fatto;
        }

        /// <summary>
        /// Effettua restore come overwrite del DB di DEFAULT... irreversibile
        /// </summary>
        /// <param name="zipFilePath">Percorso di lettura del dump cifrato (*.zip)</param>
        /// <param name="dbName">Nome del DB da processare (se vuoto quello di default calcolato)</param>
        /// <param name="tempFolder">Percorso cartella temp x esecuzione</param>
        /// <param name="mysqlPath">Nome o Percorso Eseguibile mysql (opzionale)</param>
        /// <param name="importOpt">Opzioni in fase di import (default: --force)</param>
        /// <returns></returns>
        public static bool DataBaseRestoreFromFile(string zipFilePath, string dbName = "", string tempFolder="", string mysqlPath = "mysql", string importOpt = "--force")
        {
            bool fatto = false;
            // fix path eseguibile
            if (string.IsNullOrEmpty(mysqlPath))
            {
                mysqlPath = "mysql";
            }
            if (string.IsNullOrEmpty(dbName))
            {
                dbName = DATABASE_NAME;
            }
            if (string.IsNullOrEmpty(tempFolder))
            {
                tempFolder = "C:\\Temp";
            }
            // verifica zip finale
            if (!zipFilePath.EndsWith(".zip"))
            {
                zipFilePath = $"{zipFilePath}.zip";
            }
            // elimino eventuale temp folder precedente...
            if (Directory.Exists(tempFolder))
            {
                Directory.Delete(tempFolder, true);
            }
            Directory.CreateDirectory(tempFolder);
            // scompatto file zip --> sql
            using (ZipFile zip = ZipFile.Read(zipFilePath))
            {
                zip.Password = ZIP_PWD;
                zip.ExtractAll(tempFolder, ExtractExistingFileAction.OverwriteSilently);
            }
            // verifico che ci sia 1 file sql...
            var sqlFiles = Directory.GetFiles(tempFolder, "*.sql");
            if (sqlFiles != null && sqlFiles.Length == 1)
            {
                // verifico nome file per export sql (temporaneo)
                string tempSqlPath = sqlFiles.FirstOrDefault();
                // chiamo script esterno...
                string callScript = $"\"{mysqlPath}\" -u{DATABASE_USER}  -p{DATABASE_PWD} {importOpt} {dbName} < {tempSqlPath}";
                ExecuteCommand(callScript);
                // elimino il file sql importato...
                File.Delete(tempSqlPath);
                // elimino temp folder...
                Directory.Delete(tempFolder, true);
                fatto = true;
            }
            return fatto;
        }

        /// <summary>
        /// Esecuzione di un comando esterno
        /// </summary>
        /// <param name="command"></param>
        public static void ExecuteCommand(string command)
        {
            int exitCode;
            ProcessStartInfo processInfo;
            Process process;

            processInfo = new ProcessStartInfo("cmd.exe", "/c " + command);
            processInfo.CreateNoWindow = true;
            processInfo.UseShellExecute = false;
            // *** Redirect the output ***
            processInfo.RedirectStandardError = true;
            processInfo.RedirectStandardOutput = true;

            process = Process.Start(processInfo);
            process.WaitForExit();

            // *** Read the streams ***
            // Warning: This approach can lead to deadlocks, see Edit #2
            string output = process.StandardOutput.ReadToEnd();
            string error = process.StandardError.ReadToEnd();

            exitCode = process.ExitCode;

            Console.WriteLine("output>>" + (String.IsNullOrEmpty(output) ? "(none)" : output));
            Console.WriteLine("error>>" + (String.IsNullOrEmpty(error) ? "(none)" : error));
            Console.WriteLine("ExitCode: " + exitCode.ToString(), "ExecuteCommand");
            process.Close();
        }

        /// <summary>
        /// Metodo di init standard per DB in rete con Master_Key
        /// </summary>
        /// <param name="server">Indirizzo del server (tipicamente indirizzo di rete)</param>
        /// <param name="nKey">Numero chiave</param>
        /// <param name="sKey">Codice/pwd associato a chiave</param>
        /// <param name="masterKey">Numero di chiave master con cui è creato il DB</param>
        public static void InitDb(string server, string nKey, string sKey, string masterKey = "")
        {
            // se nulla metto amster come nKey...
            masterKey = string.IsNullOrEmpty(masterKey) ? nKey : masterKey;
            DATABASE_SERV = server;
            DATABASE_NAME = $"EgtBwDb_{masterKey}";
            DATABASE_USER = $"user_{nKey}";
            DATABASE_PWD = $"pwd_{sKey}";
            ZIP_PWD = $"{DATABASE_USER}:{DATABASE_PWD}:{DATABASE_USER}:{DATABASE_PWD}";
            CONNECTION_STRING = $"server={DATABASE_SERV};port=3306;database={DATABASE_NAME};uid={DATABASE_USER};pwd={DATABASE_PWD};SslMode=None";
            // stringa admin con utente root egalware...
            ADMIN_CONNECTION_STRING = $"server={DATABASE_SERV};port=3306;database=mysql;uid=root;pwd=Egalware_24068!;SslMode=none;CHARSET=utf8";
        }

        /// <summary>
        /// Aggiorna conf NLog x target DIR
        /// </summary>
        /// <param name="logDir"></param>
        public static void SetupLogDir(string logDir)
        {
            string appDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            string filePath = Path.Combine(appDir, "NLog.config");
            // leggo il file NLog
            string rawData = File.ReadAllText(filePath);

            // sostituzione livello minimo da selezione
            rawData = rawData.Replace("${basedir}", logDir);

            // ri-scrivo file NLog
            File.WriteAllText(filePath, rawData);
        }

        #endregion Public Methods
    }
}