using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using Microsoft.EntityFrameworkCore;
using Microsoft.VisualBasic.FileIO;
using MySqlConnector;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using Gremlin.GremlinUtilities;
using Gremlin.GremlinData.EntityClasses;
using static Gremlin.GremlinData.EntityClasses.Enums;

namespace Gremlin.GremlinData.DBClasses
{
    internal static class DbHelper
    {
        private static readonly DateTime FarInTheFuture = DateTime.Parse("2050-12-31t00:00:00.000000z", CultureInfo.CurrentCulture);

        public static bool CheckDatabaseConnection(string connectionString)
        {
            bool isConnected = false;
            MySqlConnection connection = null;

            try
            {
                connection = new MySqlConnection(connectionString);
                connection.Open();
                isConnected = true;
            }

            catch (ArgumentException a_ex)
            {
                _ = MessageBox.Show(a_ex.Message, "ArgumentException", MessageBoxButton.OK, MessageBoxImage.Error);
            }

            catch (MySqlException ex)
            {
                _ = MessageBox.Show($"Message: {ex.Message}\nSource: {ex.Source}\nNumber: {ex.Number}", "Connection Failed!", MessageBoxButton.OK, MessageBoxImage.Error);

                isConnected = false;

                switch (ex.Number)
                {
                    case 1042: // Unable to connect to any of the specified MySQL hosts (Check Server,Port)
                        break;
                    case 0: // Access denied (Check DB name,username,password)
                        break;
                    default:
                        break;
                }
            }

            finally
            {
                if (connection.State == ConnectionState.Open) { connection.Close(); }
            }

            return isConnected;
        }

        public static void DbBuilder()
        ///Deletes existing database and builds it from scratch (code  first).
        {
            using (GremlinContext db = new())
            {
                db.Database.EnsureDeleted();
                db.Database.EnsureCreated();
            }
        }

        public static void DbInitializer()
        /// Initialzes the Db with enum data: product lines, account types, submarkets

        {
            DateTime now = DateTime.Now;

            //PRODUCT LINES
            string[] ProductLineAbbrviations = { "XF", "LM", "XA", "RB", "GE",
                                                 "9Z", "9P", "9M", "9K", "9H",
                                                 "9F", "9E", "9C", "ZZ", "ZF",
                                                 "V1", "MB", "MA", "LI", "JW",
                                                 "CA", "BZ", "BC", "AZ", "AJ",
                                                 "AB", "AA", "8P", "89", "74",
                                                 "6P", "6G", "58", "29", "AM",
                                                 "RP", "CD", "PT", "XB", "RM",
                                                 "GS", "AT", "SR", "FS", "BD",
                                                 "UF", "3P" };
            string[] ProductLineDescriptions = { "Cell Analysis", "Lab Management", "Flow Cell", "Remarketed Instruments", " Genomics",
                                                "Drugs of Abuse", "Sample Prep/Resins", "Research Products", "Dissolution & Other", "Vacuum Products",
                                                "AA/OES", "Drugs of Abuse", "Micro GC", "Common PL & Default", "Expedite Freight",
                                                "Automation", "Particle Analysis", "Molecular Spectroscopy", "Software and Informatics", "GC Columns",
                                                "Mid Range GC", "GC-MS", "LC Columns", "Gas Phase Analysis", "ICPMS",
                                                "Bio-consumables", "Instrument Supplies", "Analytical Parts Sup", "LC-MS", "Support Services",
                                                "Software and Professinal Services", "Other Local Products", "Competitive Supplies", "LC", "Amplicon Sequencing",
                                                "Reagent Partnership", "Companion Diagnostic", "Pathology", "Biotek", "Raman Spectroscopy",
                                                "Genomics Systems", "Parallel CE Instrument", "Bio Reagents", "Flexible Spending", "Nucleic Acid",
                                                "Microfluidics", "3rd Party Product" };

            int length = ProductLineAbbrviations.Length;
            for (int i = 0; i < length; i++)
            {
                ProductLine PlToBeAdded = new()
                {
                    ProductLineCode = ProductLineAbbrviations[i],
                    ProductLineDescription = ProductLineDescriptions[i],
                    DataStatus = Status.Active.ToString(),
                    DataModificationByUser = "DbInitializer"
                };

                using (GremlinContext db = new())
                {
                    db.ProductLines.Add(PlToBeAdded);
                    db.SaveChanges();
                }
            }

            //ACCOUNT TYPES
            string[] AccountTypes = { "FPC", "FPR", "FPT", "FPV", "FPN",
                                      "FPP", "FPG", "FPX", "FPS", "FPA",
                                      "FPO", "FPW", "NPU", "NPR", "NPG",
                                      "NPH", "SUP" };
            string[] AccountTypeDescription = { "Commercial", "Contract Manufacturing CRM & Contract Research CRO", "Contract Testing", "Instrument IDR Distributor", "Instrument National Distributor",
                                                  "Foreign Trade Org", "Genomics/CSD Distributor", "Regulated Distributor", "Transactional Reseller", "Value Added Reseller",
                                                  "OEM", "Occasional resellers", "University", "Research Institute", "Government",
                                                  "Hospital", "Supplier" };

            length = AccountTypes.Length;
            for (int i = 0; i < length; i++)
            {
                AccountType ATtoBeAdded = new()
                {
                    AccountTypeCode = AccountTypes[i],
                    AccountTypeDescription = AccountTypeDescription[i],
                    DataStatus = Status.Active.ToString(),
                    DataModificationByUser = "DbInitializer",
                    DataCreationDate = now,
                    DataModificationDate = now,
                    DataValidFrom = now,
                    DataValidUntil = FarInTheFuture,
                    DataVersionNumber = 1,
                    DataVersionComment = "DbInitializer",
                };

                using (GremlinContext db = new())
                {
                    db.AccountTypes.Add(ATtoBeAdded);
                    db.SaveChanges();
                }
            }

            //SubMarkets
            string[] SubMarketCodes = { "CCH", "CEN", "CFD", "CFR", "CMS",
                                        "COT", "LPH", "LBT", "LGN", "LRS",
                                        "DCT", "LOT", "VEN", "VAR", "SUP" };

            string[] SubMarketDescriptions = { "Chemical & Energy", "Environmental", "Food", "Forensics", "Material Science",
                                              "CA Other", "Pharmaceutical", "BioPharma", "Generics", "LS Research",
                                              "Clinical Testing", "LS Other", "Analytical Instrument Vendor", "VAR-Partner", "Supplier" };

            length = SubMarketCodes.Length;
            for (int i = 0; i < length; i++)
            {
                SubMarket SMtoBeAdded = new()
                {
                    SubMarketCode = SubMarketCodes[i],
                    SubMarketDescription = SubMarketDescriptions[i],
                    DataStatus = Status.Active.ToString(),
                    DataModificationByUser = "DbInitializer",
                    DataCreationDate = now,
                    DataModificationDate = now,
                    DataValidFrom = now,
                    DataValidUntil = FarInTheFuture,
                    DataVersionNumber = 1,
                    DataVersionComment = "DbInitializer",
                };

                using (GremlinContext db = new())
                {
                    db.SubMarkets.Add(SMtoBeAdded);
                    db.SaveChanges();
                }
            }

            List<Account> seedAccounts = new();
            Account agilent = new()
            {
                AccountName = "Agilent Technologies",
                AccountId = 1,
                Street = "Hewlett-Packard-Str. 8",
                ZIP = 76337,
                City = "Waldbronn",
                SAPAccountNumber = 00000001,
                EMail = "CustomerCare_Germany@agilent.com",
                PhoneNumber = "+49 800 6031000",
                DataStatus = Status.Active.ToString(),
                AccountType = new AccountType()
                {
                    AccountTypeCode = "SUP"
                },
                SubMarket = new SubMarket()
                {
                    SubMarketCode = "VEN"
                }
            };
            seedAccounts.Add(agilent);

            Account bios = new()
            {
                AccountName = "Bios Analytical Ltd.",
                AccountId = 2,
                Street = "7 South Fens Enterprise Park//Fenton Way//PE16 6WA Chatteris//Cambridgeshire//United Kingdom",
                ZIP = 0,
                City = "Chatteris, UNITED KINGDOM",
                SAPAccountNumber = 00000002,
                EMail = "",
                PhoneNumber = "+44 1354 694377",
                DataStatus = Status.Active.ToString(),
                AccountType = new AccountType()
                {
                    AccountTypeCode = "SUP"
                },
                SubMarket = new SubMarket()
                {
                    SubMarketCode = "VAR"
                }
            };
            seedAccounts.Add(bios);

            Account huber = new()
            {
                AccountName = "Huber Kältemaschinenbau AG",
                AccountId = 3,
                Street = "Werner-von-Siemens-Straße 1",
                ZIP = 77656,
                City = "Offenburg",
                SAPAccountNumber = 00000003,
                EMail = "sales@huber-online.com",
                PhoneNumber = "0781 96030",
                DataStatus = Status.Active.ToString(),
                AccountType = new AccountType()
                {
                    AccountTypeCode = "SUP"
                },
                SubMarket = new SubMarket()
                {
                    SubMarketCode = "SUP"
                }
            };
            seedAccounts.Add(huber);

            Account vanderheijden = new()
            {
                AccountName = "Van der Heijden Labortechnik GmbH",
                AccountId = 4,
                Street = "Tramsmeiers Berg 2",
                ZIP = 32694,
                City = "Dörentrup",
                SAPAccountNumber = 00000004,
                EMail = "info@vdh-online.com",
                PhoneNumber = "05265 945520",
                DataStatus = Status.Active.ToString(),
                AccountType = new AccountType()
                {
                    AccountTypeCode = "SUP"
                },
                SubMarket = new SubMarket()
                {
                    SubMarketCode = "SUP"
                }
            };
            seedAccounts.Add(vanderheijden);

            Account ole = new()
            {
                AccountName = "OLE DICH Instrumentmakers ApS",
                AccountId = 5,
                Street = "18, Taarnfalkevej",
                ZIP = 2650,
                City = "Hvidovre, DENMARK",
                SAPAccountNumber = 00000005,
                EMail = "",
                PhoneNumber = "+45 36 78 41 85",
                DataStatus = Status.Active.ToString(),
                AccountType = new AccountType()
                {
                    AccountTypeCode = "SUP"
                },
                SubMarket = new SubMarket()
                {
                    SubMarketCode = "SUP"
                }
            };
            seedAccounts.Add(ole);

            Account sprep = new()
            {
                AccountName = "S-prep GmbH",
                AccountId = 6,
                Street = "Im Amann 7",
                ZIP = 88662,
                City = "Überlingen",
                SAPAccountNumber = 00000006,
                EMail = "info@s-prep.com",
                PhoneNumber = "07551 932696",
                FaxNumber = "07551-932699",
                DataStatus = Status.Active.ToString(),
                AccountType = new AccountType()
                {
                    AccountTypeCode = "SUP"
                },
                SubMarket = new SubMarket()
                {
                    SubMarketCode = "SUP"
                }
            };
            seedAccounts.Add(sprep);

            Account hellma = new()
            {
                AccountName = "Hellma GmbH & Co. KG",
                AccountId = 7,
                Street = "Klosterrunsstraße 5",
                ZIP = 79379,
                City = "Müllheim",
                SAPAccountNumber = 00000007,
                EMail = "info.de@hellma.com",
                PhoneNumber = "07631 1820",
                DataStatus = Status.Active.ToString(),
                AccountType = new AccountType()
                {
                    AccountTypeCode = "SUP"
                },
                SubMarket = new SubMarket()
                {
                    SubMarketCode = "SUP"
                }
            };
            seedAccounts.Add(hellma);

            Account Glasside = new()
            {
                AccountName = "Glasside Technologies",
                AccountId = 8,
                Street = "Albany House, 82 Avenue Road//PE19 1LH St Neots, Cambridgeshire//United Kingdom",
                ZIP = 0,
                City = "St Neots, Cambridgesgire, UNITED KINGDOM",
                SAPAccountNumber = 00000008,
                EMail = "",
                PhoneNumber = "-/-",
                DataStatus = Status.Active.ToString(),
                AccountType = new AccountType()
                {
                    AccountTypeCode = "SUP"
                },
                SubMarket = new SubMarket()
                {
                    SubMarketCode = "SUP"
                }
            };
            seedAccounts.Add(Glasside);

            Account pike = new()
            {
                AccountName = "PIKE Technologies Inc.",
                AccountId = 9,
                Street = "6125 Cottonwood Drive//Madison, WI 53719//USA",
                ZIP = 53719,
                City = "Madison (WI), USA",
                SAPAccountNumber = 00000009,
                EMail = "info@piketech.com",
                PhoneNumber = "+1 608 274 2721",
                FaxNumber = "+1 608 274 0103",
                DataStatus = Status.Active.ToString(),
                AccountType = new AccountType()
                {
                    AccountTypeCode = "SUP"
                },
                SubMarket = new SubMarket()
                {
                    SubMarketCode = "SUP"
                }
            };
            seedAccounts.Add(pike);


            using (GremlinContext db = new())
            {
                foreach (Account account in seedAccounts)
                {
                    // AccountType und SubMarket aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen.
                    account.AccountType = ResolveAccountType(db, account.AccountType.AccountTypeCode);
                    account.SubMarket = ResolveSubmarket(db, account.SubMarket.SubMarketCode);
                    account.DataCreationDate = now;
                    account.DataModificationDate = now;
                    account.DataValidFrom = now;
                    account.DataValidUntil = FarInTheFuture;
                    account.DataVersionNumber = 1;
                    account.DataModificationByUser = "DB Initializer";
                    account.DataVersionComment = "Function DbHelper.DbInitializer";
                }

                db.Accounts.AddRange(seedAccounts);
                _ = db.SaveChanges();
            }

            //UserSetting
            //List<LoggedInUser> seedUserSettings = new();

            //LoggedInUser userSascha = new("Dr. Sascha Woitschetzki",
            //                            "+49 208 74129134",
            //                            "+49 176 22285334",
            //                            "sascha.woitschetzki@non.agilent.com",
            //                            "83PE89",
            //                            6000,
            //                            "Tex");
            //seedUserSettings.Add(userSascha);

            //LoggedInUser userSebastian = new("Dr. Sebastian Welsch",
            //                                "+49 2222 9611441",
            //                                "+49 160 7523241",
            //                                "sebastian.welsch@non.agilent.com",
            //                                "83RE32",
            //                                1324,
            //                                "Word");
            //seedUserSettings.Add(userSebastian);

            //using (GremlinContext db = new())
            //{
            //    foreach (LoggedInUser userSetting in seedUserSettings)
            //    {
            //        userSetting.DataCreationDate = now;
            //        userSetting.DataModificationByUser = "DB Initializer";
            //        userSetting.DataModificationDate = now;
            //    }
            //    db.UserSetting.AddRange(seedUserSettings);
            //    _ = db.SaveChanges();
            //}
        }

        public static void InsertTestDataIntoDB()
        {
            using (GremlinContext db = new())
            {
                DateTime now;
                now = DateTime.Now;

                Random r = new();

                Contact newContact = new()
                {
                    AcademicTitle = RandomString(9),
                    FirstName = RandomString(5),
                    LastName = RandomString(20),
                    Gender = (byte)Gender.Male,
                    Department = RandomString(9),
                    DataStatus = Status.Active.ToString(),
                    DataModificationByUser = "Tester",
                    DataCreationDate = now,
                    DataModificationDate = now,
                    DataValidFrom = now,
                    DataValidUntil = FarInTheFuture,
                    DataVersionNumber = 1,
                    DataVersionComment = "Seeding data for testing.",
                };

                Account newAccount = new()
                {
                    AccountName = RandomString(20),
                    Street = RandomString(9),
                    ZIP = Convert.ToUInt32(r.Next(11111, 99999)),
                    City = RandomString(9),
                    SAPAccountNumber = Convert.ToUInt32(r.Next(1111111, 99999999)),
                    EMail = RandomString(9) + "@" + RandomString(9) + "." + RandomString(3),
                    PhoneNumber = "+49 " + r.Next().ToString(),
                    DataStatus = Status.Active.ToString(),
                    DataModificationByUser = "Tester",
                    DataCreationDate = now,
                    DataModificationDate = now,
                    DataValidFrom = now,
                    DataValidUntil = FarInTheFuture,
                    DataVersionNumber = 1,
                    DataVersionComment = "Seeding data for testing.",
                };

                AccountType accountType = db.AccountTypes
                    .Where(a => a.AccountTypeCode == "FPC")
                    .FirstOrDefault();
                newAccount.AccountType = accountType;

                SubMarket subMarket = db.SubMarkets
                    .Where(a => a.SubMarketCode == "CEN")
                    .First();
                newAccount.SubMarket = subMarket;
                newContact.Account = newAccount;

                try
                {
                    _ = db.Add(newAccount);
                    _ = db.SaveChanges();
                    _ = db.Add(newContact);
                    _ = db.SaveChanges();
                    _ = MessageBox.Show($"Account {newAccount.AccountName} mit Contact {newContact.LastName} wurde angelegt.");
                }
                catch (Exception ex)
                {
                    ErrorHandler.ShowErrorInMessageBox(ex);
                }
            }
        }

        public static bool ImportContactsFromCsv(string filepath = "", string separator = ";", bool dataHasHeading = true)
        {
            //Pfad abfragen über Dtei-Öffnen-Dialog:
            if (filepath == "") filepath = FileIO.GetFilepathFromUser();

            //Wenn keine Datei ausgewählt (Cancel geklickt), dann Abbruch:
            if (filepath == "") return false;


            List<Contact> ContactsReadFromFile = new(8000);
            // ENCODING CHANGED TO UNICODE. TO BE TESTED!
            using (TextFieldParser csvParser = new(filepath, FileIO.GetEncoding(filepath)))
            {
                //Parser configuration:
                csvParser.Delimiters = new string[] { separator };
                csvParser.CommentTokens = new string[] { "#" };
                csvParser.HasFieldsEnclosedInQuotes = true;


                // Skip the row with the column names:
                if (dataHasHeading) csvParser.ReadLine();

                while (!csvParser.EndOfData)
                {
                    // Read current line fields, pointer moves to the next line.
                    Contact ImportedContact = new();
                    string[] fields = csvParser.ReadFields();

                    //No conversion
                    ImportedContact.AcademicTitle = fields[1];
                    ImportedContact.FirstName = fields[3];
                    ImportedContact.LastName = fields[4];
                    ImportedContact.EMail = fields[6];
                    ImportedContact.Department = fields[7];
                    ImportedContact.Room = fields[8];
                    ImportedContact.PhoneNumber = fields[9];
                    ImportedContact.Function = fields[10];
                    ImportedContact.MobileNumber = fields[13];

                    //Convert Gender
                    ImportedContact.Gender = fields[2] == "M" ? (byte)Gender.Male : fields[2] == "F" ? (byte)Gender.Female : (byte)Gender.Unknown;

                    //Convert OptIn Status
                    ImportedContact.OptInStatus = fields[5] switch
                    {
                        "Opt In" => true,
                        "Opt Out" => false,
                        _ => null,
                    };

                    //Convert "SAP Contact Number"
                    try
                    {
                        ImportedContact.SAPContactNumber = Convert.ToInt32(fields[11], CultureInfo.CurrentCulture);
                    }
                    catch (FormatException ex)
                    {
                        //errorhandling here: "Unable to parse input: " + field[0] + " as uint"
                        //Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
                        string errorRaiser = fields[11];
                        if (errorRaiser == "") errorRaiser = "No Contact Number in this row!";
                        DisplayErrorDetails(ex, errorRaiser);
                        return false;
                    }
                    catch (OverflowException ex)
                    {
                        //errorhandling here: "Number cannot fit in an Uint."
                        //Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
                        string errorRaiser = fields[11];
                        if (errorRaiser == "") errorRaiser = "No Contact Number in this row!";
                        DisplayErrorDetails(ex, errorRaiser);
                        return false;
                    }

                    //Convert "Account Created on"
                    int year = Convert.ToInt32(fields[12].Substring(0, 4), CultureInfo.CurrentCulture);
                    int month = Convert.ToInt32(fields[12].Substring(4, 2), CultureInfo.CurrentCulture);
                    int day = Convert.ToInt32(fields[12].Substring(6, 2), CultureInfo.CurrentCulture);
                    ImportedContact.SAPContactCreationDate = new DateTime(year, month, day);

                    //Convert "No Phone Calls"
                    if (fields[26] == "1") ImportedContact.NoPhoneCalls = true;

                    //Convert "No Hardcopy Mailing"
                    if (fields[27] == "1") ImportedContact.NoHardcopyMailing = true;

                    //SAPAccountID in Contact.Notes speichern, um den entsprechenden Account aus DB heraussuchen zu können:
                    ImportedContact.Notes = fields[0];

                    ContactsReadFromFile.Add(ImportedContact);
                }

                //Eingelesenen Account in DB schreiben:
                using (GremlinContext db = new())
                {
                    DateTime now;
                    now = DateTime.Now;

                    foreach (Contact contact in ContactsReadFromFile)
                    {
                        // AccountID aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen.
                        contact.Account = ResolveAccountById(db, contact.Notes);
                        contact.Notes = "";
                        contact.DataModificationByUser = "CSVImporter";
                        contact.DataStatus = Status.Active.ToString();
                        contact.DataVersionNumber = 1;
                        contact.DataVersionComment = "Initial import by CSV Importer (Function DbHelper.ImportContactsFromCSV)";
                        contact.DataValidFrom = now;
                        contact.DataValidUntil = FarInTheFuture;
                        contact.DataCreationDate = now;
                        contact.DataModificationDate = now;
                    }

                    db.Contacts.AddRange(ContactsReadFromFile);
                    db.SaveChanges();
                }
            }
            //Bestätigung senden
            MessageBox.Show($"Es wurden {ContactsReadFromFile.Count} Contacts erfolgreich der Datenbank hinzugefügt.");
            return true;
        }

        public static bool ImportAccountsFromCsv(string filepath = "", string separator = ";", bool dataHasHeading = true)
        ///Importiert Account Daten aus CSV (erstellt aus LSAG_Contact_List_Tool.xlsx)
        ///
        ///     - Encoding: Latin1
        ///     - fixe Spaltenzahl und -folge (Überschriften werden nicht untersucht/verwendet):
        ///         1) SAPAccountNumber
        ///         2) AccountName
        ///         3) ZIP
        ///         4) City
        ///         5) Street
        ///         6) AccountSubMarketCode
        ///         7) AccountTypeCode
        ///         8) PhoneNumber
        ///         9) AccountsCreatedInSAP
        ///Kommentarzeichen: # (hardcoded, string array)
        ///Rückgabe 'false' bei Fehler oder User-Abbruch, ansonsten 'true'.
        ///
        ///Optionale Argumente: 
        ///  1. string filepath:       Dateipfad zur einzulesenden Datei. Standardwert = "", öffnet OpenFile-Dialog.
        ///  2. string separator:      Trennzeichen, Standardwert = ";"
        ///  3. bool dataHasheadings:  true = CSV-Datei enthält Überschriften, false = CSV-Datei enthält keine Überschriften, Standardwert = true
        {
            //Pfad abfragen über Dtei-Öffnen-Dialog:
            if (filepath == "") filepath = FileIO.GetFilepathFromUser();

            //Wenn keine Datei ausgewählt (Cancel geklickt), dann Abbruch:
            if (filepath == "") return false;

            List<Account> AccountsReadFromFile = new(1000);

            //ENCODING CHANGED TO ISO-8859-1 AS EQUIVALENT (?) TO "LATIN1" (unavailable). TO BE TESTED!
            //ich würde mir ja UTF8 als universelles Encoding wünschen

            using (TextFieldParser csvParser = new(filepath, FileIO.GetEncoding(filepath)))
            {
                //Parser configuration:
                csvParser.Delimiters = new string[] { separator };
                csvParser.CommentTokens = new string[] { "#" };
                csvParser.HasFieldsEnclosedInQuotes = true;

                // Skip the row with the column names:
                if (dataHasHeading) csvParser.ReadLine();

                while (!csvParser.EndOfData)
                {
                    // Read current line fields, pointer moves to the next line.
                    Account ImportedAccount = new();
                    {
                        ImportedAccount.SubMarket = new();
                        ImportedAccount.AccountType = new();
                        ImportedAccount.Contacts = new List<Contact>();
                    }
                    string[] fields = csvParser.ReadFields();

                    //Konvertierung erforderlich:
                    try
                    {
                        ImportedAccount.SAPAccountNumber = Convert.ToUInt32(fields[0]);
                    }
                    catch (FormatException ex)
                    {
                        //errorhandling here: "Unable to parse input: " + field[0] + " as uint"
                        //Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
                        string errorRaiser = fields[0];
                        if (errorRaiser == "") errorRaiser = "No Account Number in this row!";
                        DisplayErrorDetails(ex, errorRaiser);
                        return false;
                    }
                    catch (OverflowException ex)
                    {
                        //errorhandling here: "Number cannot fit in an Uint."
                        //Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
                        string errorRaiser = fields[0];
                        if (errorRaiser == "") errorRaiser = "No Account Number in this row!";
                        DisplayErrorDetails(ex, errorRaiser);
                        return false;
                    }

                    try
                    {
                        ImportedAccount.ZIP = Convert.ToUInt32(fields[2]);
                    }
                    catch (FormatException ex)
                    {
                        //errorhandling here: "Unable to parse input: " + field[0] + " as uint"
                        //Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
                        string errorRaiser = fields[0];
                        if (errorRaiser == "") errorRaiser = "No ZIP in this row!";
                        DisplayErrorDetails(ex, errorRaiser);
                        return false;
                    }
                    catch (OverflowException ex)
                    {
                        //errorhandling here: "Number cannot fit in an Uint."
                        //Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
                        string errorRaiser = fields[0];
                        if (errorRaiser == "") errorRaiser = "No ZIP in this row!";
                        DisplayErrorDetails(ex, errorRaiser);
                        return false;
                    }

                    //Konvertierung für AccountCreatedinSAPOn Property:
                    //Test auf Inhalt
                    if (fields[8].Length != 0)
                    {
                        int year = Convert.ToInt32(fields[8].Substring(0, 4));
                        int month = Convert.ToInt32(fields[8].Substring(4, 2));
                        int day = Convert.ToInt32(fields[8].Substring(6, 2));
                        ImportedAccount.AccountCreatedInSAPOn = new DateTime(year, month, day);
                    }

                    //keine Konvertierung nötig:
                    ImportedAccount.AccountName = fields[1];
                    ImportedAccount.City = fields[3];
                    ImportedAccount.Street = fields[4];
                    ImportedAccount.SubMarket.SubMarketCode = fields[5];
                    ImportedAccount.SubMarket.DataStatus = Status.Active.ToString();
                    ImportedAccount.AccountType.AccountTypeCode = fields[6];
                    ImportedAccount.AccountType.DataStatus = Status.Active.ToString();
                    ImportedAccount.PhoneNumber = fields[7];
                    ImportedAccount.DataStatus = Status.Active.ToString();

                    AccountsReadFromFile.Add(ImportedAccount);
                }

                //Eingelesenen Account in DB schreiben:
                using (GremlinContext db = new())
                {
                    DateTime now = DateTime.Now;

                    foreach (Account account in AccountsReadFromFile)
                    {
                        // AccountType aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen.
                        if (account.AccountType.AccountTypeCode != "")
                        {
                            AccountType accountType = db.AccountTypes
                            .Where(a => a.AccountTypeCode == account.AccountType.AccountTypeCode)
                            .First();

                            account.AccountType = accountType;
                            account.AccountType = ResolveAccountType(db, account.AccountType.AccountTypeCode);
                        }
                        else
                        {
                            //Default
                            account.AccountType = ResolveAccountType(db, "FPC");
                        }

                        if (account.SubMarket.SubMarketCode != "")
                        {
                            SubMarket subMarket = db.SubMarkets
                            .Where(a => a.SubMarketCode == account.SubMarket.SubMarketCode)
                            .First();

                            account.SubMarket = subMarket;
                            //Warum gelöscht? account.AccountType = ResolveAccountType(db, account.AccountType.AccountTypeCode);
                            account.SubMarket = ResolveSubmarket(db, account.SubMarket.SubMarketCode);

                        }
                        else
                        {
                            //Default
                            account.SubMarket = ResolveSubmarket(db, "COT");
                        }

                        account.DataValidFrom = now;
                        account.DataValidUntil = FarInTheFuture;
                        account.DataStatus = Status.Active.ToString();
                        account.DataCreationDate = now;
                        account.DataModificationDate = now;
                        account.DataModificationByUser = "CSVImporter";
                        account.DataVersionNumber = 1;
                        account.DataVersionComment = "Initial import by CSV Importer (Function DbHelper.ImportAccountsFromCSV)";
                    }

                    db.Accounts.AddRange(AccountsReadFromFile);
                    db.SaveChanges();

                    //Bestätigung senden
                    MessageBox.Show($"Es wurden {AccountsReadFromFile.Count} Accounts erfolgreich der Datenbank hinzugefügt.");
                }
            }
            return true;
        }

        public static bool ImportLSAGContactListToolData(string filepath = "", string separator = ";")
        ///Importiert Accounts und Contacts aus CSV in die DB

        {
            if (filepath == "") filepath = FileIO.GetFilepathFromUser();               //Pfad abfragen über Dtei-Öffnen-Dialog:
            if (filepath == "") return false;                                   //Wenn keine Datei ausgewählt (Cancel geklickt), dann Abbruch:

            List<Contact> ContactsReadFromFile = new(8000);
            List<Account> AccountsReadFromFile = new(1000);

            using (TextFieldParser csvParser = new(filepath, FileIO.GetEncoding(filepath)))   //ISO-8859-1 entspricht Latin1
            {
                //Parser configuration:
                csvParser.Delimiters = new string[] { separator };
                csvParser.CommentTokens = new string[] { "#" };
                csvParser.HasFieldsEnclosedInQuotes = true;

                //dynamische Spaltenzuordnung in Dictonary speichern
                Dictionary<string, int> columnNumberOf = new();
                string[] fields = csvParser.ReadFields();
                columnNumberOf = ColumnMapping(fields);
                //Daten einlesen und Accounts und Kontakte in eigene List<Account> bzw List<Contact> extrahieren.

                while (!csvParser.EndOfData)
                {
                    bool DataHasError = false;
                    Account ImportedAccount = new();
                    Contact ImportedContact = new();
                    ImportedAccount.SubMarket = new();
                    ImportedAccount.AccountType = new();
                    ImportedContact.Account = new();

                    fields = csvParser.ReadFields();            // Read current line fields, pointer moves to the next line.
                    if (fields[0] == "") break;                 // Am Ende hängt eine leere Zeile, die im Parser einen Fehler auslösen würde.

                    //Account-Daten
                    //  Konvertierung erforderlich:
                    try
                    {
                        ImportedAccount.SAPAccountNumber = Convert.ToUInt32(fields[columnNumberOf["SAPAccountNumber"]]);
                    }
                    catch (FormatException ex)
                    {
                        //errorhandling here: "Unable to parse input: " + field[0] + " as uint"
                        //Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
                        string errorRaiser = fields[columnNumberOf["SAPContactNumber"]];
                        if (errorRaiser == "") errorRaiser = "No Contact Number in this row!";
                        DisplayErrorDetails(ex, errorRaiser);
                        return false;
                    }
                    catch (OverflowException ex)
                    {
                        //errorhandling here: "Number cannot fit in an Uint."
                        //Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
                        string errorRaiser = fields[columnNumberOf["SAPContactNumber"]];
                        if (errorRaiser == "") errorRaiser = "No Contact Number in this row!";
                        DisplayErrorDetails(ex, errorRaiser);
                        return false;
                    }

                    try
                    {
                        ImportedAccount.ZIP = Convert.ToUInt32(fields[columnNumberOf["ZIP"]]);
                    }
                    catch (FormatException ex)
                    {
                        //errorhandling here: "Unable to parse input: " + field[0] + " as uint"
                        //Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
                        string errorRaiser = fields[columnNumberOf["SAPContactNumber"]];
                        if (errorRaiser == "") errorRaiser = "No Contact Number in this row!";
                        DisplayErrorDetails(ex, errorRaiser);
                        return false;
                    }
                    catch (OverflowException ex)
                    {
                        //errorhandling here: "Number cannot fit in an Uint."
                        //Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
                        string errorRaiser = fields[columnNumberOf["SAPContactNumber"]];
                        if (errorRaiser == "") errorRaiser = "No Contact Number in this row!";
                        DisplayErrorDetails(ex, errorRaiser);
                        return false;
                    }

                    //  Konvertierung für AccountCreatedinSAPOn Property:
                    int year = Convert.ToInt32(fields[columnNumberOf["AccountCreatedInSAPOn"]].Substring(0, 4));
                    int month = Convert.ToInt32(fields[columnNumberOf["AccountCreatedInSAPOn"]].Substring(4, 2));
                    int day = Convert.ToInt32(fields[columnNumberOf["AccountCreatedInSAPOn"]].Substring(6, 2));
                    ImportedAccount.AccountCreatedInSAPOn = new DateTime(year, month, day);

                    //Convert City von Großschreibung zu Normalschreibung
                    ImportedAccount.City = fields[columnNumberOf["City"]].First().ToString() + fields[columnNumberOf["City"]].Substring(1).ToLower();

                    //  keine Konvertierung nötig:
                    ImportedAccount.AccountName = fields[columnNumberOf["AccountName"]];
                    ImportedAccount.Street = fields[columnNumberOf["Street"]];
                    ImportedAccount.SubMarket.SubMarketCode = fields[columnNumberOf["SubMarketCode"]];
                    ImportedAccount.SubMarket.DataStatus = Status.Active.ToString();
                    ImportedAccount.AccountType.AccountTypeCode = fields[columnNumberOf["AccountTypeCode"]];
                    ImportedAccount.AccountType.DataStatus = Status.Active.ToString();
                    ImportedAccount.PhoneNumber = fields[columnNumberOf["AccountPhoneNumber"]];
                    ImportedAccount.DataStatus = Status.Active.ToString();


                    //Validierungen:
                    if (ImportedAccount.AccountName == ""
                        || ImportedAccount.City == ""
                        || ImportedAccount.Street == ""
                        || ImportedAccount.SubMarket.SubMarketCode == ""
                        || ImportedAccount.AccountType.AccountTypeCode == ""
                        || ImportedAccount.SAPAccountNumber == 0)
                    {
                        DataHasError = true;
                    }

                    //Validierten Account der Liste hinzufügen:
                    if (DataHasError == false) ImportedAccount.AddIfUniqueTo(AccountsReadFromFile);


                    //Contact-Daten
                    //  Ohne Konvertierung
                    ImportedContact.AcademicTitle = fields[columnNumberOf["AcademicTitle"]];
                    ImportedContact.FirstName = fields[columnNumberOf["FirstName"]];
                    ImportedContact.LastName = fields[columnNumberOf["LastName"]];
                    ImportedContact.EMail = fields[columnNumberOf["EMail"]];
                    ImportedContact.Department = fields[columnNumberOf["Department"]];
                    ImportedContact.Room = fields[columnNumberOf["Room"]];
                    ImportedContact.PhoneNumber = fields[columnNumberOf["PhoneNumber"]];
                    ImportedContact.Function = fields[columnNumberOf["Function"]];
                    ImportedContact.MobileNumber = fields[columnNumberOf["MobileNumber"]];

                    //Convert Gender
                    ImportedContact.Gender = fields[columnNumberOf["Gender"]] == "M"
                        ? (byte)Gender.Male
                        : fields[columnNumberOf["Gender"]] == "F" ? (byte)Gender.Female : (byte)Gender.Unknown;

                    //Convert OptIn Status
                    ImportedContact.OptInStatus = fields[columnNumberOf["OptInStatus"]] == "Opt In"
                        ? true
                        : fields[columnNumberOf["OptInStatus"]] == "Opt Out" ? false : null;

                    //Convert "SAP Contact Number"
                    try
                    {
                        ImportedContact.SAPContactNumber = Convert.ToInt32(fields[columnNumberOf["SAPContactNumber"]]);
                    }
                    catch (FormatException ex)
                    {
                        //errorhandling here: "Unable to parse input: " + field[0] + " as uint"
                        //Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
                        string errorRaiser = fields[columnNumberOf["SAPContactNumber"]];
                        if (errorRaiser == "") errorRaiser = "No Contact Number in this row!";
                        DisplayErrorDetails(ex, errorRaiser);
                        return false;
                    }
                    catch (OverflowException ex)
                    {
                        //errorhandling here: "Number cannot fit in an Uint."
                        //Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
                        string errorRaiser = fields[columnNumberOf["SAPContactNumber"]];
                        if (errorRaiser == "") errorRaiser = "No Contact Number in this row!";
                        DisplayErrorDetails(ex, errorRaiser);
                        return false;
                    }

                    //Convert "Account Created on"
                    year = Convert.ToInt32(fields[columnNumberOf["AccountCreatedInSAPOn"]].Substring(0, 4));
                    month = Convert.ToInt32(fields[columnNumberOf["AccountCreatedInSAPOn"]].Substring(4, 2));
                    day = Convert.ToInt32(fields[columnNumberOf["AccountCreatedInSAPOn"]].Substring(6, 2));
                    ImportedContact.SAPContactCreationDate = new DateTime(year, month, day);

                    //Convert "No Phone Calls"
                    if (fields[columnNumberOf["NoPhoneCalls"]] == "1") ImportedContact.NoPhoneCalls = true;

                    //Convert "No Hardcopy Mailing"
                    if (fields[columnNumberOf["NoHardcopyMailing"]] == "1") ImportedContact.NoHardcopyMailing = true;

                    //SAPAccountID in Contact.Notes speichern, um den entsprechenden Account aus DB heraussuchen zu können:
                    ImportedContact.Notes = fields[columnNumberOf["SAPAccountNumber"]];

                    //Validierungen:
                    if (ImportedContact.EmailBounced || ImportedContact.LastName == "" || ImportedContact.SAPContactNumber == 0)
                        DataHasError = true;

                    if (ImportedContact.EMail.Length > 7 && ImportedContact.EMail.Substring(0, 8) == "bounced-")
                        DataHasError = true;

                    //Validierte Kontakte zu Liste hinzufügen:
                    if (DataHasError == false) ContactsReadFromFile.Add(ImportedContact);
                }

                //Eingelesenen Account in DB schreiben:
                using (GremlinContext db = new())
                {
                    DateTime now;
                    now = DateTime.Now;

                    foreach (Account account in AccountsReadFromFile)
                    {
                        // AccountType aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen.
                        AccountType accountType = db.AccountTypes
                            .Where(a => a.AccountTypeCode == account.AccountType.AccountTypeCode)
                            .First();
                        account.AccountType = accountType;

                        SubMarket subMarket = db.SubMarkets
                            .Where(a => a.SubMarketCode == account.SubMarket.SubMarketCode)
                            .First();
                        account.SubMarket = subMarket;
                        account.AccountType = ResolveAccountType(db, account.AccountType.AccountTypeCode);
                        account.SubMarket = ResolveSubmarket(db, account.SubMarket.SubMarketCode);

                        account.DataValidFrom = now;
                        account.DataValidUntil = FarInTheFuture;
                        account.DataCreationDate = now;
                        account.DataModificationDate = now;
                        account.DataModificationByUser = "CSVImporter";
                        account.DataVersionNumber = 1;
                        account.DataVersionComment = "Initial import by CSV Importer (Function DbHelper.ImportAccountsFromCSV)";
                        account.DataStatus = Status.Active.ToString();
                    }

                    db.Accounts.AddRange(AccountsReadFromFile);
                    _ = db.SaveChanges();
                }

                //Eingelesenen Contacts in DB schreiben:
                using (GremlinContext db = new())
                {
                    DateTime now;
                    now = DateTime.Now;

                    foreach (Contact contact in ContactsReadFromFile)
                    {
                        contact.Account = ResolveAccountById(db, contact.Notes);
                        contact.Notes = "";
                        contact.DataModificationByUser = "CSVImporter";
                        contact.DataVersionNumber = 1;
                        contact.DataVersionComment = "Initial import by CSV Importer (Function DbHelper.ImportContactsFromCSV)";
                        contact.DataStatus = Status.Active.ToString();
                        contact.DataValidFrom = now;
                        contact.DataValidUntil = FarInTheFuture;
                        contact.DataCreationDate = now;
                        contact.DataModificationDate = now;
                    }

                    db.Contacts.AddRange(ContactsReadFromFile);
                    _ = db.SaveChanges();
                }
            };
            return true;
        }

        public static bool ImportProductsFromCsv(string filepath = "", string separator = "|", bool dataHasHeading = true)
        ///Rückgabe 'false' bei Fehler oder User-Abbruch, ansonsten 'true'.
        ///
        ///Optionale Argumente: 
        ///  1. string filepath:       Dateipfad zur einzulesenden Datei. Standardwert = "", öffnet OpenFile-Dialog.
        ///  2. string separator:      Trennzeichen, Standardwert = ";"
        ///  3. bool dataHasheadings:  true = CSV-Datei enthält Überschriften, false = CSV-Datei enthält keine Überschriften, Standardwert = true
        {
            if (filepath == "") filepath = FileHelper.GetFilepathFromUser();       //Pfad abfragen über Dtei-Öffnen-Dialog.
            if (filepath == "") return false;               //Wenn keine Datei ausgewählt (Cancel geklickt), dann Abbruch.

            List<Product> ProductsReadFromFile = new(ParseProductFile(filepath, separator, dataHasHeading));
            return InsertProducts(ProductsReadFromFile);
        }

        public static bool ImportCustomDescriptionsFromCsv(string filepath = "", string separator = "|", bool dataHasHeading = true)
        ///Importiert Eigene Beschreibungen aus Word-Dokument
        ///Abkürzung: CD = CustomDescriptions
        {
            if (filepath == "") filepath = FileHelper.GetFilepathFromUser("Delimited Data File|*.csv; *.txt; *.tsv");   //Pfad abfragen über Dtei-Öffnen-Dialog:
            if (filepath == "") return false;                       //Wenn keine Datei ausgewählt (Cancel geklickt), dann Abbruch:

            List<CustomDescription> CDsReadFromFile = new(2500);
            using (TextFieldParser csvParser = new(filepath, Encoding.GetEncoding("UTF-8")))
            {
                //Parser configuration:
                csvParser.Delimiters = new string[] { separator };
                csvParser.CommentTokens = new string[] { "#" };
                csvParser.HasFieldsEnclosedInQuotes = true;


                // Skip the row with the column names:
                if (dataHasHeading) _ = csvParser.ReadLine();

                while (!csvParser.EndOfData)
                {
                    // Read current line fields, pointer moves to the next line.
                    string[] fields = csvParser.ReadFields();
                    CustomDescription ImportedCD = new()
                    {
                        ProductNumber = fields[0],
                        OptionNumber = fields[1] == "" ? null : fields[1],
                        Heading = fields[3],
                        DescriptionText = fields[4],
                        CoverletterText = fields[5],
                        Notes = fields[6],
                        DataModificationByUser = "Importer",
                        DataStatus = Status.Active.ToString(),
                        DataValidUntil = FarInTheFuture,
                        DataVersionComment = "Initial Importer by CD-ImporterFomCsv",
                    };
                    ImportedCD.Product = new();
                    ImportedCD.Supplier = new();
                    ImportedCD.Supplier.AccountName = fields[2] is "" or "RB" ? "Agilent Technologies" : fields[2];
                    ImportedCD.DataCreationDate = ImportedCD.DataValidFrom = ImportedCD.DataModificationDate = DateTime.Now;
                    ImportedCD.DataVersionNumber = 1;

                    CDsReadFromFile.Add(ImportedCD);
                }

                //Eingelesenen Custum Desciptions in DB schreiben:
                //Check for 3PPs that not yet in the db.
                //Step 1a: Add 3PP-Products from CD-List to list
                //Step 1b: Add list to db.
                //Step 2: Add CDs to products.

                using (GremlinContext db = new())
                {
                    //Step 1a
                    List<Product> thirdPartyProductsFromImportedCDs = new();
                    foreach (CustomDescription CD in CDsReadFromFile)
                    {
                        if (CD.Supplier.AccountName != "Agilent Technologies")
                        {
                            Product new3PPProduct = new();

                            new3PPProduct.CustomDescription = CD;
                            new3PPProduct.HasBreakPrices = false;
                            new3PPProduct.ListPrice = 0;
                            new3PPProduct.ProductNumber = CD.ProductNumber;
                            new3PPProduct.OptionNumber = CD.OptionNumber;
                            new3PPProduct.ProductStatus = Status.Active.ToString();
                            new3PPProduct.SapLongDescription = null;
                            new3PPProduct.SapShortDescription = null;
                            new3PPProduct.Weight = 0;
                            new3PPProduct.IntroductionDate = DateTime.Now.Date;
                            new3PPProduct.DataVersionNumber = 1;
                            new3PPProduct.DataVersionComment = "Created at import from Custom Descriptions.";
                            new3PPProduct.DataStatus = Status.Active.ToString();
                            new3PPProduct.DataModificationByUser = "Custom Desciption Importer";
                            new3PPProduct.BreakRangeFrom = 0;
                            new3PPProduct.BreakRangeTo = 0;
                            new3PPProduct.ProductLine = ResolveProductLine(db, "3P");
                            new3PPProduct.CustomDescription.Supplier = ResolveAccountByName(db, new3PPProduct.CustomDescription.Supplier.AccountName);

                            thirdPartyProductsFromImportedCDs.Add(new3PPProduct);
                        }
                    };

                    //Step 1b
                    db.Products.AddRange(thirdPartyProductsFromImportedCDs);        //Imports both the products and the associated custom descriptions!
                    _ = db.SaveChanges();

                    // Produkt aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen.
                    //Step 2
                    List<CustomDescription> importedCDsWithEFReferences = new(100000);
                    List<CustomDescription> CDsWithoutEFReferences = new(1000);     //nur zur Kontrolle, wird nicht verwendet.
                    List<Product> productsInDb = db.Products.ToList();
                    Account agilent = db.Accounts.Where(a => a.AccountName == "Agilent Technologies").Single();
                    foreach (CustomDescription CD in CDsReadFromFile)
                    {
                        //Skip Desciptions, if it has been already imported above (as part from 3PP)
                        if (thirdPartyProductsFromImportedCDs.Contains(CD.Product)) continue;

                        //Establish EF Reference. If no PN/Opt found, then skip this custom description.
                        //CD.Product = GetProduct(db, CD.ProductNumber, CD.OptionNumber);         //ResolveXY-functions return null, if no match is found in db.
                        CD.Product = productsInDb.Find(p => p.ProductNumber == CD.ProductNumber && p.OptionNumber == CD.OptionNumber);
                        if (CD.Product == null)
                        {
                            CDsWithoutEFReferences.Add(CD);
                            continue;
                        }

                        //Establish EF Reference: If no Supplier-Account found, then skip this custom description (shouldn't happen), else set Supplier to Agilent (3PP-Supplier have been processed before and their products should not be part of this list).
                        if (CD.Supplier == null)
                        {
                            CDsWithoutEFReferences.Add(CD);
                            continue;
                        }
                        CD.Supplier = agilent;

                        //prepare properties
                        CD.DataStatus = Status.Active.ToString();
                        CD.DataVersionNumber = 1;
                        CD.DataModificationByUser = "CSVImporter";
                        CD.DataVersionComment = "Initial import by CSV Importer (Function DbHelper.ImportCustomDescriptionsFromDocx)";

                        //add to final list of CDs, that will go into the db.
                        importedCDsWithEFReferences.Add(CD);
                    }

                    db.CustomDescriptions.AddRange(importedCDsWithEFReferences);
                    db.SaveChanges();
                    //Bestätigung senden
                    MessageBox.Show($"Es wurden {importedCDsWithEFReferences.Count} eigene Beschreibungen erfolgreich der Datenbank hinzugefügt.{Environment.NewLine}Es wurden {thirdPartyProductsFromImportedCDs.Count} 3PP-Produkte neu angelegt.");
                }
                return true;
            }
        }

        public static bool ImportCustomDescriptionsFromDocx(string filepath = "")
        ///Importiert Eigene Beschreibungen aus Word-Dokument
        ///Abkürzung: CD = CustomDescriptions
        {
            if (filepath == "") filepath = FileHelper.GetFilepathFromUser("Word-Dokument|*.doc; *.docx; *.docm");   //Pfad abfragen über Dtei-Öffnen-Dialog:
            if (filepath == "") return false;                       //Wenn keine Datei ausgewählt (Cancel geklickt), dann Abbruch:

            List<CustomDescription> importedCDs = new(2500);
            WordprocessingDocument CDDoc = WordprocessingDocument.Open(filepath, false);     //alternativ in using-Block packen, dann kann man nicht das Document.Close vergessen.
            Table CDTable = CDDoc.MainDocumentPart.Document.Body.Elements<Table>().ElementAt(0);  //alternativ: Elements<Table>.First()
            if (CDTable != null)
            {
                CDTable.Descendants<TableRow>().First().Remove();       //Überschriften löschen
                foreach (TableRow aRow in CDTable.Descendants<TableRow>())   //einmal durch den XML-Baum wandern: WordprocessingDocument -> MainDocumentPart -> Document -> Body -> Table -> TableRow -> TableCell -> Paragraph -> Run -> Text
                {
                    CustomDescription importedCD = new();
                    List<string> stringsRead = new();

                    foreach (TableCell aCell in aRow.Descendants<TableCell>())
                    {
                        StringBuilder sb = new();
                        StringBuilder note = new();
                        foreach (Paragraph aParagraph in aCell.Descendants<Paragraph>())
                        {
                            foreach (Run aRun in aParagraph.Descendants<Run>())
                            {
                                foreach (Text aText in aRun.Descendants<Text>())
                                {
                                    string temp = "";
                                    temp = aParagraph.Descendants<Run>()
                                                    .Where(r => r.RunProperties.Vanish != null)
                                                    .Aggregate("", (text, r) => text += r.InnerText);      //nach hidden Text filtern, diesen in "note" speichern
                                    _ = note.Append(temp);

                                    if (temp == "")                                                        //true, falls kein hidden Text => normalen Text verketten...
                                    {
                                        _ = sb.Append(aText.InnerText);
                                    }
                                }
                            }
                        }
                        stringsRead.Add(sb.ToString());                                                     //...und zellenweise abspeichern
                        if (note.ToString() != "")
                        {
                            importedCD.Notes = note.ToString();
                        }
                    }

                    importedCD.ProductNumber = stringsRead.ElementAt(0);
                    importedCD.OptionNumber = stringsRead.ElementAt(1) == "" ? null : stringsRead.ElementAt(1);
                    importedCD.Supplier.AccountName = stringsRead.ElementAt(2) == "" || stringsRead.ElementAt(2) == "RB" ? "Agilent Technologies" : stringsRead.ElementAt(2);
                    importedCD.Heading = stringsRead.ElementAt(3);
                    importedCD.DescriptionText = stringsRead.ElementAt(4);
                    importedCD.CoverletterText = stringsRead.ElementAt(5);

                    importedCDs.Add(importedCD);
                    if (importedCDs.Count >= 1200 && importedCDs.Count % 100 == 0) MessageBox.Show(importedCDs.Count.ToString());     //DEBUGGING

                }
            }
            //MessageBox.Show(importedCDs.Count.ToString()); //DEBUGGING
            CDDoc.Close();


            //Eingelesenen Custum Desciptions in DB schreiben:
            //Check for 3PPs that not yet in the db.
            //Step 1a: Add 3PP-Products from CD-List to list
            //Step 1b: Add list to db.
            //Step 2: Add CDs to products.

            using (GremlinContext db = new())
            {
                //Step 1a
                List<Product> thirdPartyProductsFromImportedCDs = new();
                foreach (CustomDescription CD in importedCDs)
                {
                    if (CD.Supplier.AccountName != "Agilent Technologies")
                    {
                        Product new3PPProduct = new();
                        new3PPProduct.CustomDescription = CD;
                        new3PPProduct.HasBreakPrices = false;
                        new3PPProduct.ListPrice = 0;
                        new3PPProduct.ProductNumber = CD.ProductNumber;
                        new3PPProduct.OptionNumber = CD.OptionNumber;
                        new3PPProduct.ProductStatus = Status.Active.ToString();
                        new3PPProduct.SapLongDescription = null;
                        new3PPProduct.SapShortDescription = null;
                        new3PPProduct.Weight = 0;
                        new3PPProduct.IntroductionDate = DateTime.Now.Date;
                        new3PPProduct.DataVersionNumber = 1;
                        new3PPProduct.DataVersionComment = "Created at import from Custom Descriptions.";
                        new3PPProduct.DataStatus = Status.Active.ToString();
                        new3PPProduct.DataModificationByUser = "Custom Desciption Importer";
                        new3PPProduct.BreakRangeFrom = 0;
                        new3PPProduct.BreakRangeTo = 0;
                        new3PPProduct.ProductLine = ResolveProductLine(db, "3P");
                        new3PPProduct.CustomDescription.Supplier = ResolveAccountByName(db, new3PPProduct.CustomDescription.Supplier.AccountName);

                        thirdPartyProductsFromImportedCDs.Add(new3PPProduct);
                    }
                };

                //Step 1b
                db.Products.AddRange(thirdPartyProductsFromImportedCDs);        //Imports both the products and the associated custom descriptions!
                _ = db.SaveChanges();

                // Produkt aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen.
                //Step 2
                List<CustomDescription> importedCDsWithEFReferences = new();
                List<CustomDescription> CDsWithoutEFReferences = new();     //nur zur Kontrolle, wird nicht verwendet.
                foreach (CustomDescription CD in importedCDs)
                {
                    //Skip Desciptions, if it has been already imported above (as part from 3PP)
                    if (thirdPartyProductsFromImportedCDs.Contains(CD.Product)) continue;

                    //Establish EF Reference. If no PN/Opt found, then skip this custom description.
                    CD.Product = ResolveProduct(db, CD.ProductNumber, CD.OptionNumber);         //ResolveXY-functions return null, if no match is found in db.
                    if (CD.Product == null)
                    {
                        CDsWithoutEFReferences.Add(CD);
                        continue;
                    }

                    //Establish EF Reference. If no Supplier-Account found, then skip this custom description.
                    CD.Supplier = ResolveAccountByName(db, CD.Supplier.AccountName);            //ResolveXY-functions return null, if no match is found in db.
                    if (CD.Supplier == null)
                    {
                        CDsWithoutEFReferences.Add(CD);
                        continue;
                    }

                    //prepare properties
                    CD.DataStatus = Status.Active.ToString();
                    CD.DataVersionNumber = 1;
                    CD.DataModificationByUser = "CSVImporter";
                    CD.DataVersionComment = "Initial import by CSV Importer (Function DbHelper.ImportCustomDescriptionsFromDocx)";

                    //add to final list of CDs, that will go into the db.
                    importedCDsWithEFReferences.Add(CD);
                }

                db.CustomDescriptions.AddRange(importedCDsWithEFReferences);
                _ = db.SaveChanges();
            }
            return true;
        }

        public static bool UpdateProductsFromCsv(string filepath = "", string separator = "|", bool dataHasHeading = true)
        {
            if (filepath == "") filepath = FileIO.GetFilepathFromUser();   //Pfad abfragen über Dtei-Öffnen-Dialog.
            string fileName = ExtractFileName(filepath);
            if (filepath == "") return false;          //Wenn keine Datei ausgewählt (Cancel geklickt), dann Abbruch.

            //Aktiven Produkte aus DB laden
            List<Product> ActiveProductsInDb = new(120000);
            using (GremlinContext db = new())
            {
                //Aktive Produkte aus der DB einlesen
                List<ProductLine> PLs = db.ProductLines.ToList();
                Microsoft.EntityFrameworkCore.Query.IIncludableQueryable<Product, ProductLine> query = db.Products
                    .Where(p => p.DataStatus == Status.Active.ToString())
                    .Include(p => p.ProductLine);
                //var querystring = query.ToQueryString();
                ActiveProductsInDb = query.ToList();

                //Neue CPL einlesen...
                List<Product> ProductsReadFromFile = new(ParseProductFile(filepath, separator, dataHasHeading));

                //...Aufteilung in neue und nicht-neue Produkte...
                List<Product> NewProducts = new(FilterNewProducts(ProductsReadFromFile));
                List<Product> NonNewProducts = new(ProductsReadFromFile.Except(NewProducts));

                //...Letztere wiederum in obsolete Produkte...
                ProductEqualityComparer ProEC = new();
                List<Product> ObsoleteProducts = new(ActiveProductsInDb.Except(NonNewProducts, ProEC));

                DateTime now = DateTime.Now;

                //Obsolete Produkte prozessieren
                foreach (Product product in ObsoleteProducts)
                {
                    product.ProductStatus = Status.Obsolete.ToString();
                    product.DataStatus = Status.Archived.ToString();
                    product.DataValidUntil = now;
                    product.DataModificationDate = now;
                    product.DataModificationByUser = "CPL Updater";
                }

                //den Rest prozessieren
                List<Product> UpdatedProducts = new(10000);
                string dataVersionComment = "Data as per " + fileName;

                ////Counter für Debugging
                //int x = 0;

                foreach (Product product in NonNewProducts)
                {
                    ////Debugging-Counter
                    //x++;

                    Product ExistingProduct = ActiveProductsInDb
                        .FirstOrDefault(p => p.ProductNumber == product.ProductNumber
                                             && p.OptionNumber == product.OptionNumber
                                             && p.BreakRangeFrom == product.BreakRangeFrom
                                             && p.BreakRangeTo == product.BreakRangeTo);

                    //Checkpoint, falls ein neues Produkt versehentlich auf "Active" statt auf "New" gesetzt ist.
                    if (ExistingProduct == null)
                    {
                        NewProducts.Add(product);
                        continue;
                    }

                    //Wenn keine Änderung, dann nur DataVersionComment mit CPL-Dateinamen aktualisieren...
                    if (product.ListPrice == ExistingProduct.ListPrice
                        && product.ProductStatus == ExistingProduct.ProductStatus
                        && Normalize(product.SapLongDescription) == Normalize(ExistingProduct.SapLongDescription)
                        && Normalize(product.SapShortDescription) == Normalize(ExistingProduct.SapShortDescription))
                    {
                        ExistingProduct.DataVersionComment = dataVersionComment;
                    }
                    //andernfalls die vorhandene Datensatz-Version archivieren und neue Version anlegen
                    else   //alten Datensatz archivieren:
                    {
                        ExistingProduct.DataStatus = Status.Archived.ToString();
                        ExistingProduct.DataModificationByUser = "CPL Updater";
                        ExistingProduct.DataModificationDate = now;
                        ExistingProduct.DataValidUntil = now;
                        ExistingProduct.DataVersionComment = "Archived b/c of data update";
                        if (product.ListPrice != ExistingProduct.ListPrice)
                        {
                            ExistingProduct.ProductStatus = Status.PriceUpdated.ToString();
                        }
                        else if (Normalize(product.SapLongDescription) != Normalize(ExistingProduct.SapLongDescription)
                                || Normalize(product.SapShortDescription) != Normalize(ExistingProduct.SapShortDescription))
                        {
                            ExistingProduct.ProductStatus = Status.DescriptionUpdated.ToString();
                        }
                        else if (product.ProductStatus == ExistingProduct.ProductStatus)
                        {
                            ExistingProduct.ProductStatus = Status.StatusUpdated.ToString();
                        }


                        //neuen Datensatz anlegen
                        Product UpdatedProduct = new()
                        {
                            ProductNumber = product.ProductNumber,
                            OptionNumber = product.OptionNumber,
                            DataVersionNumber = ExistingProduct.DataVersionNumber + 1,
                            DataCreationDate = now,
                            DataModificationByUser = "CPL Updater",
                            DataModificationDate = now,
                            DataStatus = Status.Active.ToString(),
                            DataValidFrom = now,
                            DataValidUntil = FarInTheFuture,
                            DataVersionComment = "Product Update",
                            BreakRangeFrom = product.BreakRangeFrom,
                            BreakRangeTo = product.BreakRangeTo,
                            HasBreakPrices = product.HasBreakPrices,
                            IntroductionDate = ExistingProduct.IntroductionDate,
                            ListPrice = product.ListPrice,

                            //ProductLine = ResolveProductLine(db, product.ProductLine.ProductLineAbbreviation),

                            ProductStatus = Status.Active.ToString(),
                            SapLongDescription = product.SapLongDescription,
                            SapShortDescription = product.SapShortDescription,
                            Weight = product.Weight,
                        };

                        UpdatedProduct.ProductLine = product.ProductLine.ProductLineCode == ExistingProduct.ProductLine.ProductLineCode
                            ? product.ProductLine
                            : ResolveProductLine(db, product.ProductLine.ProductLineCode);

                        NewProducts.Add(UpdatedProduct);
                    }
                    UpdatedProducts.Add(ExistingProduct);
                }

                _ = InsertProducts(NewProducts);    //enthält auch ResolvePL()
                db.Products.UpdateRange(ObsoleteProducts);
                db.Products.UpdateRange(UpdatedProducts);
                int changes = db.SaveChanges();
                Debug.WriteLine($"Es wurden {changes} Änderungen an der Datenbank durchgeführt.");
            }

            return true;
        }
        
        private static Dictionary<string, int> ColumnMapping(string[] fields)
        {
            Dictionary<string, int> result = new();
            for (int i = 0; i < fields.Length; i++)
            {
                switch (fields[i]
                            .ToLower(CultureInfo.CurrentCulture)
                            .Trim()
                            .Replace(" ", "")
                            .Replace("-", "")
                            .Replace("_", "")
                            .Replace(".", "")
                            .Replace(":", "")
                            )
                {
                    case "accountid" or "fcustomercd" or "sapaccountnumber":
                        result.Add("SAPAccountNumber", i);
                        break;
                    case "accountname" or "fcustomername":
                        result.Add("AccountName", i);
                        break;
                    case "postalcode" or "fpostcode" or "zip":
                        result.Add("ZIP", i);
                        break;
                    case "city" or "ftown":
                        result.Add("City", i);
                        break;
                    case "street" or "faddress":
                        result.Add("Street", i);
                        break;
                    case "title" or "ftitletext1":
                        result.Add("AcademicTitle", i);
                        break;
                    case "gender" or "fgender1":
                        result.Add("Gender", i);
                        break;
                    case "firstname" or "ffirstname":
                        result.Add("FirstName", i);
                        break;
                    case "lastname" or "flastname":
                        result.Add("LastName", i);
                        break;
                    case "optin" or "femailopt":
                        result.Add("OptInStatus", i);
                        break;
                    case "workemail" or "fworkemail1" or "accountemail":
                        result.Add("EMail", i);
                        break;
                    case "bouncedbackemail" or "fbouncedback":
                        result.Add("EmailBounced", i);
                        break;
                    case "department" or "fdepartment1":
                        result.Add("Department", i);
                        break;
                    case "roomnumber" or "froomnumber":
                        result.Add("Room", i);
                        break;
                    case "workphone" or "fworkphone1" or "phonenumber":
                        result.Add("PhoneNumber", i);
                        break;
                    case "faxnumber":
                        result.Add("FaxNumber", i);
                        break;
                    case "function" or "ffunction":
                        result.Add("Function", i);
                        break;
                    case "contactid" or "fcontactcd":
                        result.Add("SAPContactNumber", i);
                        break;
                    case "contactcreationdate" or "fcreationdate":
                        result.Add("SAPContactCreationDate", i);
                        break;
                    case "mobilephone" or "fmobilephone":
                        result.Add("MobileNumber", i);
                        break;
                    case "jobfunction" or "fjobfunction":
                        result.Add("MA_JobFunctions", i);
                        break;
                    case "productinterest" or "fproductinterest":
                        result.Add("MA_ProductInterests", i);
                        break;
                    case "applicationinterest" or "fapplicationinterest":
                        result.Add("MA_ApplicationInterests", i);
                        break;
                    case "joblevel" or "fjoblevel":
                        result.Add("MA_JobLevel", i);
                        break;
                    case "contactindustry" or "fcontactindustry":
                        result.Add("MA_ContactIndustry", i);
                        break;
                    case "competitiveibase" or "fcompetativeibase":
                        result.Add("MA_CompetitiveIBase", i);
                        break;
                    case "submarketcode" or "fsubmarketcd":
                        result.Add("SubMarketCode", i);
                        break;
                    case "customertypecode" or "fcustomertypecd" or "accounttype" or "accounttypecode":
                        result.Add("AccountTypeCode", i);
                        break;
                    case "phone" or "fphone":
                        result.Add("AccountPhoneNumber", i);
                        break;
                    case "accountcreatedon" or "fcreatedon":
                        result.Add("AccountCreatedInSAPOn", i);
                        break;
                    case "contactchangedon" or "fcontactchangedon":
                        result.Add("SAPContactModifiedDate", i);
                        break;
                    case "notelephonecalls" or "fnotelephonecalls":
                        result.Add("NoPhoneCalls", i);
                        break;
                    case "nohardcopymailing" or "fnohardcopymailing":
                        result.Add("NoHardcopyMailing", i);
                        break;
                    case "accounttypedescription":
                        result.Add("AccountTypeDescription", i);
                        break;
                    case "submarketdescription":
                        result.Add("SubMarketDescription", i);
                        break;
                    case "productlineabbreviation":
                        result.Add("ProductLineAbbreviation", i);
                        break;
                    case "productlinedescription":
                        result.Add("ProductLineDescription", i);
                        break;
                    default:
                        break;
                }
            }
            return result;
        }

        private static List<Product> ParseProductFile(string filepath, string separator, bool dataHasHeading)
        ///Importiert Produkt-Daten aus CSV (erstellt aus PriceSurfer-CPL)
        ///     - Encoding: Latin1
        ///     - Separator = ;
        ///     - fixe Spaltenzahl und -folge (Überschriften werden nicht untersucht/verwendet):
        ///         1) Position = ID/PK
        ///         2) Partnumber
        ///         3) Option
        ///         4) (Short) Description
        ///         5) Current Month Price (EUR)
        ///         6) ProductLineID
        ///         7) Status
        ///         8) (Long) Description
        ///Kommentarzeichen: # (hardcoded, string array)
        {
            List<Product> results = new(100000);
            //ENCODING CHANGED TO ISO-8859-1 AS EQUIVALENT (?) TO "LATIN1" (unavailable). TO BE TESTED!
            using (TextFieldParser csvParser = new(filepath, FileHelper.GetEncoding(filepath)))
            {
                //Parser configuration:
                csvParser.Delimiters = new string[] { separator };
                csvParser.CommentTokens = new string[] { "#" };
                csvParser.HasFieldsEnclosedInQuotes = true;

                // Skip the row with the column names:
                if (dataHasHeading) _ = csvParser.ReadLine();

                //decimal oldListPrice;
                //bool listpriceHasChanged;
                while (!csvParser.EndOfData)
                {
                    // Read current line fields, pointer moves to the next line.
                    Product ImportedProduct = new();
                    ImportedProduct.ProductLine = new();
                    string[] fields = csvParser.ReadFields();

                    //Kontrolle, ob Trennzeichen in Daten vorhanden ist:
                    if (fields.Length > 27)   //27 ist der Normalfall
                    {
                        //Sonderfall "EnVisionTM G|2":
                        //suche fields[x], das auf " G" endet und auf das field[x+1] mit "2 " beginnt, kombiniere die beiden und schiebe alle darauffolgenden Felder in diesem Datensatz eins nach links.
                        for (int i = 0; i < fields.Length - 1; i++)  //fields.Length - 1, um durch i+1 nicht aus dem Index zu laufen
                        {
                            if (fields[i].EndsWith(" G"))
                            {
                                if (fields[i + 1].StartsWith("2 "))
                                {
                                    fields[i] = fields[i] + fields[i + 1];
                                    for (int j = i + 2; j < fields.Length; j++)
                                    {
                                        fields[j - 1] = fields[j];
                                    }
                                }
                            }
                        }
                    }

                    //NICHT ausgewertete Felder:
                    //fields[0] = Position [in CPL]
                    //fields[8] = Warranty
                    //fields[10] = PH Code
                    //fields[11] = PH Description
                    //fields[15] = Country of Manufacturing
                    //fields[16] = ECCL
                    //fields[17] = M41
                    //fields[18] = First Supplier Code
                    //fields[19] = Harmonized Tarif Schedule
                    //fields[20] = Hazardous Good Flag
                    //fields[21] = Order instructions
                    //fields[23] = End of Production Date
                    //fields[24] = End of Support Date

                    ImportedProduct.ProductNumber = fields[1];

                    if (fields[2].Length == 4)  //Optionsnummer mit führendem Apostroph
                        ImportedProduct.OptionNumber = fields[2].Substring(1); //schneidet erstes Zeichen/Apostroph weg
                    else if (fields[2].Length == 3)  //3-stellige Optionsnummer übernehmen; keine Aktion bei leerem Feld (keine Optionsnummer)
                        ImportedProduct.OptionNumber = fields[2];

                    ImportedProduct.SapShortDescription = fields[3];

                    ImportedProduct.ListPrice = decimal.Parse(fields[4], new CultureInfo("de-de")); //parsing! compare with old value (either from CSV or from DB) -> price change?

                    //if (fields[5] != "")
                    //    if (decimal.Parse(fields[5], new CultureInfo("de-de")) != ImportedProduct.ListPrice)
                    //           listpriceHasChanged = true;

                    ImportedProduct.BreakRangeFrom = fields[6] == "" ? 0 : Convert.ToInt32(fields[6]);
                    ImportedProduct.HasBreakPrices = ImportedProduct.BreakRangeFrom > 0;
                    ImportedProduct.BreakRangeTo = fields[7] is "" or "+" ? 0 : Convert.ToInt32(fields[7]);      //erfasst sowohl Produkte ohne Break-Preise ("") als auch "+" bei Mengenangaben a la "100+" (= von 100 bis unendlich)

                    ImportedProduct.ProductLine.ProductLineCode = fields[9];

                    switch (fields[12])
                    {
                        case "Active":
                            ImportedProduct.DataStatus = Status.Active.ToString();
                            ImportedProduct.ProductStatus = Status.Active.ToString();
                            break;
                        case "New Product" or "New":
                            ImportedProduct.DataStatus = Status.Active.ToString();
                            ImportedProduct.ProductStatus = Status.New.ToString();
                            break;
                        case "Price Changed":
                            ImportedProduct.DataStatus = Status.Active.ToString();
                            ImportedProduct.ProductStatus = Status.PriceUpdated.ToString();
                            break;
                        default:
                            ImportedProduct.DataStatus = Status.StatusUpdated.ToString();
                            break;
                    }

                    if (fields[14] != null) ImportedProduct.Weight = ParseWeight(fields[14]);
                    if (fields[13] == "G") ImportedProduct.Weight /= 1000; //Umrechnung g in kg

                    if (fields[22] != "") ImportedProduct.IntroductionDate = DateTime.Parse(fields[22]);

                    ImportedProduct.SapLongDescription = fields[25];

                    results.Add(ImportedProduct);
                }
            }

            return results;
        }

        public static float ParseWeight(string input)
        {
            StringBuilder sb = new();
            if (input.StartsWith("."))
            {
                input = sb.Append(0).Append(input).ToString();
            }

            try
            {
                return float.Parse(input, new CultureInfo("en-us"));
            }
            catch (Exception ex)
            {
                ErrorHandler.ShowErrorInMessageBox(ex, "Unhandled Error in Function 'ParseWeight'");
                return 0;
            }

        }

        public static bool InsertProducts(List<Product> products)
        {
            using (GremlinContext db = new())
            {
                DateTime now;
                now = DateTime.Now;
                List<ProductLine> productLines = db.ProductLines.ToList();
                foreach (Product product in products)
                {
                    //var query = db.ProductLines
                    //    .Where(a => a.ProductLineAbbreviation == product.ProductLine.ProductLineAbbreviation)
                    //    .First();
                    //product.ProductLine = query;

                    //product.ProductLine = ResolveProductLine(db, product.ProductLine.ProductLineAbbreviation);
                    product.ProductLine = productLines.Find(x => x.ProductLineCode == product.ProductLine.ProductLineCode);

                    product.DataValidFrom = now;
                    product.DataValidUntil = FarInTheFuture;
                    product.DataVersionNumber = 1;
                    product.DataVersionComment = "Initial import by CSV Importer (Function DbHelper.ImportProductsFromCSV)";
                    product.DataCreationDate = now;
                    product.DataModificationDate = now;
                    product.DataModificationByUser = "CSVImporter";
                    product.DataStatus = Status.Active.ToString();
                }

                db.Products.AddRange(products);
                db.SaveChanges();
                //Bestätigung senden
                MessageBox.Show($"Es wurden {products.Count} Produkte erfolgreich der Datenbank hinzugefügt.");
                return true;
            }
        }

        public static void DisplayErrorDetails(Exception ex, string errorRaiser)
        {
            string errorMessage = $"Source: {ex.Source}{Environment.NewLine}" +
                                  $"Message: {ex.Message}{Environment.NewLine}{Environment.NewLine}" +
                                  $"Contact: {errorRaiser}";
            MessageBox.Show(errorMessage);
        }

        internal static string ExtractFileName(string filepath, bool withExtension = false)
        {
            string[] fields = filepath.Split(@"\");
            string[] filename = fields[fields.Length - 1].Split(".");
            if (withExtension)
            {
                return fields[fields.Length - 1];
            }
            else return filename[0];
        }

        private static string Normalize(string input)
        {
            return input.ToLower()
                        .Trim()
                        .Replace(" ", "")
                        .Replace("-", "")
                        .Replace("_", "")
                        .Replace(".", "");
        }

        private static List<Product> FilterNewProducts(List<Product> products)
        {
            DateTime now = DateTime.Now;

            List<Product> results = new(5000);
            foreach (Product product in products)
            {
                if (product.ProductStatus == Status.New.ToString())
                {
                    product.DataCreationDate = now;
                    product.DataModificationDate = now;
                    product.DataValidFrom = now;
                    product.DataValidUntil = FarInTheFuture;
                    product.DataModificationByUser = "CPL Updater";
                    product.DataVersionNumber = 1;
                    product.DataVersionComment = "Initial Import by CSV Importer/Updater";
                    results.Add(product);
                }
            }
            return results;
        }

        //private static List<Product> FilterProductsWithPriceChange(List<Product> products)
        //{
        //    List<Product> results = new();
        //    foreach (var product in products)
        //    {
        //        if (product.ProductStatus == Status.PriceUpdated.ToString())
        //        {
        //            results.Add(product);
        //        }
        //    }
        //    return results;
        //}

        public static async Task<bool> ImportLSAGContactListToolDataAsync()
        {
            return await Task.Run(() => ImportLSAGContactListToolData());
        }

        public static async Task<bool> ImportAccountsFromCsvAsync()
        {
            return await Task.Run(() => ImportAccountsFromCsv());
        }

        public static async Task<bool> ImportContactsFromCsvAsync()
        {
            return await Task.Run(() => ImportContactsFromCsv());
        }

        public static async Task<bool> ImportProductsFromCsvAsync()
        {
            return await Task.Run(() => ImportProductsFromCsv());
        }

        public static async Task<bool> ImportCustomDescriptionsFromDocxAsync()
        {
            return await Task.Run(() => ImportCustomDescriptionsFromDocx());
        }

        public static async Task<bool> ImportCustomDescriptionsFromCsvAsync()
        {
            return await Task.Run(() => ImportCustomDescriptionsFromCsv());
        }
        public static async Task<bool> UpdateProductsFromCsvAsync()
        {
            return await Task.Run(() => UpdateProductsFromCsv());
        }

        public static Account ResolveAccountByName(GremlinContext context, string AccName)
        {
            try
            {
                Account query = context.Accounts
                    .Include(a => a.AccountType)
                    .Include(a => a.SubMarket)
                    .Where(a => a.AccountName == AccName)
                    .First();
                return query;
            }
            catch
            {
                return null;
            }
        }

        public static Account ResolveAccountById(GremlinContext context, string accountId)
        {
            try { return context.Accounts.Where(account => account.SAPAccountNumber == accountId).First(); }
            catch { return null; }
        }

        public static AccountType ResolveAccountType(GremlinContext context, string AccTypeCode)
        {
            try
            {
                AccountType query = context.AccountTypes
                    .Where(a => a.AccountTypeCode == AccTypeCode)
                    .First();
                return query;
            }
            catch
            {
                return null;
            }
        }

        public static SubMarket ResolveSubmarket(GremlinContext context, string subMarketCode)
        {
            try
            {
                SubMarket query = context.SubMarkets
                    .Where(a => a.SubMarketCode == subMarketCode)
                    .First();
                return query;
            }
            catch
            {
                return null;
            }
        }

        public static Product GetProduct(GremlinContext context, string pn, string option)
        {
            try
            {
                Product query = context.Products
                    .Include(p => p.ProductLine)
                    .Include(p => p.CustomDescription)
                    .Where(a => a.ProductNumber == pn && a.OptionNumber == option)
                    .First();
                return query;
            }
            catch
            {
                return null;
            }
        }

        public static Product ResolveProduct(GremlinContext context, string pn, string option)
        {
            try
            {
                Product query = context.Products
                    .Where(a => a.ProductNumber == pn && a.OptionNumber == option)
                    .First();
                return query;
            }
            catch
            {
                return null;
            }
        }

        public static ProductLine ResolveProductLine(GremlinContext context, string plCode)
        {
            try
            {
                ProductLine query = context.ProductLines
                .Where(a => a.ProductLineCode == plCode)
                .First();
                return query;
            }
            catch
            {
                return null;
            }
        }

        private static readonly Random random = new();
        public static string RandomString(int length)
        {
            const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
            return new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray());
        }
    }

    public class ProductEqualityComparer : IEqualityComparer<Product>
    {
        public bool Equals(Product product1, Product product2)
        {
            return !((product1 != null || product2 != null)
                && !(product1 != null && product2 != null
                && product1.ProductNumber == product2.ProductNumber
                && product1.OptionNumber == product2.OptionNumber
                && product1.BreakRangeFrom == product2.BreakRangeFrom
                && product1.BreakRangeTo == product2.BreakRangeTo));
        }

        public int GetHashCode(Product product)
        {
            StringBuilder sb = new();
            _ = sb.Append(product.ProductNumber).Append(product.OptionNumber).Append(product.BreakRangeFrom).Append(product.BreakRangeTo);
            return sb.ToString().GetHashCode();
        }
    }
}
