using Gremlin.GremlinData.EntityClasses;
using Gremlin.GremlinUtilities;
using Gremlin.GremlinUtilities.GUClasses;
using Microsoft.VisualBasic.FileIO;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using static Gremlin.GremlinData.EntityClasses.Enums;

namespace Gremlin.GremlinData.DBClasses
{
    public static class GenericImporter
    {
        //Private members
        private static readonly DateTime FarInTheFuture = DateTime.Parse("2050-12-31t00:00:00.000000z", CultureInfo.CurrentCulture);
        
        private static TextFieldParser _csvParser;
        private static string _filepath;
        private static bool _dataHasHeadings;
        private static Encoding _encoding = default;
        internal static GremlinContext db = new();

        //Public properties
        public static string Filepath 
        { 
            get => _filepath;
            set
            {
                _filepath = value;
                if (_encoding != null)
                {
                    TextFieldParser _csvParser = new(Filepath, _encoding, true);
                    _csvParser.SetDelimiters(Separators);
                    _csvParser.HasFieldsEnclosedInQuotes = true;
                }
            }
        }

        public static string[] Separators
        {
            get => _csvParser?.Delimiters;
            set
            {
                if (value.Length > 0)
                {
                    _csvParser?.SetDelimiters(value);
                }
            }
        }
        
        public static bool DataHasHeadings
        {
            get => _dataHasHeadings;
            set => _dataHasHeadings = value;
        }

        public static Encoding Encoding
        {
            get => _encoding;
            set
            {
                _encoding = value;
                ResetParser();
            }
        }

        ////Constructors
        //public GenericImporter(string filepath)
        //{
        //    Filepath = filepath;
        //    _dataHasHeadings = true;
        //    _encoding = FileHelper.GetEncoding(Filepath);
        //    TextFieldParser _csvParser = new(Filepath, _encoding);
        //    _csvParser.SetDelimiters(Separators);
        //    _csvParser.HasFieldsEnclosedInQuotes = true;
        //}

        //public GenericImporter(string filepath, Encoding encoding)
        //{
        //    Filepath = filepath;
        //    _dataHasHeadings = true;
        //    _encoding = encoding;
        //    TextFieldParser _csvParser = new(Filepath, _encoding);
        //    _csvParser.SetDelimiters(Separators);
        //    _csvParser.HasFieldsEnclosedInQuotes = true;
        //}

        //public GenericImporter(string filepath, string[] separators)
        //{
        //    Filepath = filepath;
        //    _dataHasHeadings = true;
        //    _encoding = FileHelper.GetEncoding(Filepath);
        //    TextFieldParser _csvParser = new(Filepath, _encoding);
        //    Separators = separators;
        //    _csvParser.HasFieldsEnclosedInQuotes = true;
        //}
        //public GenericImporter(string filepath, Encoding encoding, string[] separators)
        //{
        //    Filepath = filepath;
        //    _encoding = encoding;
        //    _dataHasHeadings = true;
        //    TextFieldParser _csvParser = new(Filepath, _encoding);
        //    Separators = separators; 
        //    _csvParser.HasFieldsEnclosedInQuotes = true;
        //}


        //Public methods
        public static void SetFilepath()
        {
            Filepath = FileHelper.GetFilepathFromUser();
        }

        public static void ResetParser()
        {
            if (Filepath != "")
            {
                TextFieldParser _newParser = new(Filepath, _encoding);
                _newParser.SetDelimiters(Separators);
                _newParser.HasFieldsEnclosedInQuotes = true;
                _csvParser = _newParser;
            }
        }

        public static bool Run(string filepath, string separator, string encoding)
        {
            _filepath = filepath.Replace(@"\\", @"\");
            try
            {
                Encoding = Encoding.GetEncoding(encoding);
            }
            catch (Exception)
            {
                Encoding = FileHelper.GetEncoding(_filepath);
            }
            Separators = new string[] { separator };
            
            ImportFile();
            return true;
        }

        public static async Task<bool> RunAsync(string filepath, string separator, string encoding)
        {
            if (filepath == "" || filepath == null) return false;
            _filepath = filepath?.Replace(@"\\", @"\");
            try
            {
                Encoding = Encoding.GetEncoding(encoding);
            }
            catch (Exception)
            {
                Encoding = FileHelper.GetEncoding(_filepath);
            }
            Separators = new string[] { separator };

            return await Task.Run(() => ImportFile());
        }

        public static string GuessSeparator(string filepath, string encoding)
        {
            if (filepath == "" || filepath == null) return null;
            string[] candidates = new string[] { "|", ",", ";" };
            int numberOfCandidates = candidates.Length;
            int numberOfLinesToEvaluate = 100;
            int[,] score = new int[numberOfLinesToEvaluate, numberOfCandidates];

            _filepath = filepath.Replace(@"\\", @"\");
            if (_csvParser == null)
            {
                try
                {
                    Encoding = Encoding.GetEncoding(encoding);
                }
                catch (Exception)
                {
                    Encoding = FileHelper.GetEncoding(_filepath);
                }
            }

            using (_csvParser)
            {
                string line;
                for (int i = 0; i < numberOfLinesToEvaluate; i++)
                {
                    if (!_csvParser.EndOfData)
                    {
                        line = _csvParser.ReadLine();
                        for (int j = 0; j < numberOfCandidates; j++)
                        {
                            score[i, j] = line.Split(candidates[j]).Length;
                        }
                    }
                }                
            }

            List<(string, int, float)> scoreBoard = new(); //Item1 = Separator, Item2 = Score (Anzahl aufeinanderfolgender Zeilen mit gleicher Anzahl von Fields), Item3 = Count (durchschnittliche Anzahl von Fields in Zeile)
            int x;
            float average;
            for (int j = 0; j < numberOfCandidates; j++)
            {
                x = 0;
                average = 0;
                for (int i = 0; i < numberOfLinesToEvaluate - 1; i++)
                {
                    if (score[i,j] == score[i+1,j] && score[i,j] > 1)
                    {
                        x++;
                    }
                    average += score[i, j];
                }
                average += score[numberOfLinesToEvaluate - 1, j];
                average /= numberOfLinesToEvaluate;
                scoreBoard.Add((candidates[j], x, average));
            }
            
            ResetParser();
            return scoreBoard.Find(f => f.Item2 == scoreBoard.Max(x => x.Item2) && f.Item3 == scoreBoard.Max(x => x.Item3)).Item1;
        }
                
        public static bool ImportFile()
        //Ein (möglichst) generischer Importer
        //1. Dateipfad erfassen
        //2. Column <-> Property Mapping
        //3. Typ der zu importierenden Daten herausfinden
        //4. Daten einlesen, konvertieren, validieren, Metadaten setzen und alles in Liste(n) speichern
        //5. Datenliste(n) in DB speichern
        {
            if (_filepath == "") SetFilepath();
            if (_filepath == "") return false;

            using (_csvParser)
            {
               //für geneerischen Code:
                //int numberOfLines = File.ReadAllLines(filepath).Length;
                //Assembly Gremlin = Assembly.GetExecutingAssembly();
                
                
                //dynamische Spaltenzuordnung in Dictonary speichern
                string[] fields = _csvParser.ReadFields();
                Dictionary<string, string> MappingDictionary = ReadMappingDictionaryFromFile();
                Dictionary<string, int> mappingTable = MapDataHeading(fields, MappingDictionary);

                //determine data type to be imported
                DataIdentificator dataIdentificator = new(mappingTable);
                List<string> detectedDataTypes = dataIdentificator.Identify();

                //check for detectedDataTypes = empty (possible when DataIdentificator.Identify(MustMatchAllQualifer = true) and no dataset matches all qualifiers of a type)
                if (detectedDataTypes.Count == 0)
                {
                    MessageBox.Show($"DataIdentificator.Identify() konnte die Datenart nicht bestimmen!{Environment.NewLine}Versuchen Sie den Import mit einer dezidierten Importerfunktion.");
                    return false;
                }

                switch (detectedDataTypes[0])
                {
                    case "ProductLine":
                        ImportProductLine(_csvParser, mappingTable);
                        break;
                    case "AccountType":
                        ImportAccountType(_csvParser, mappingTable);
                        break;
                    case "SubMarket":
                        ImportSubMarket(_csvParser, mappingTable);
                        break;
                    case "Account":
                        ImportAccounts(mappingTable);
                        break;
                    case "Contact":
                        ImportContacts(mappingTable);
                        break;
                    case "LSAG":
                        ImportLSAG(mappingTable);
                        break;
                    case "Product":
                        ImportProducts(mappingTable);
                        break;
                    case "CustomDescription":
                        ImportCustomDescriptions(mappingTable);
                        break;
                    default:
                        return false;
                }
            }
            return true;
        }

        public static bool ImportFile(string filepath)
        {
            _filepath = filepath;
            return ImportFile();
        }
        
        private static bool ImportCustomDescriptions(Dictionary<string, int> mappingTable)
        {
            List<CustomDescription> CDsReadFromFile = new(2500);
            Encoding = Encoding.GetEncoding("UTF-8");  //Custom-Descriptions-CSV hat festes Encoding.
            using (_csvParser)
            {
                // Skip the row with the column names:
                _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.BreakRangeFrom = 0;
                            new3PPProduct.BreakRangeTo = 0;
                            new3PPProduct.ProductLine = DbHelper.ResolveProductLine(db, "3P");
                            new3PPProduct.CustomDescription.Supplier = DbHelper.ResolveAccountByName(db, new3PPProduct.CustomDescription.Supplier.AccountName);
                            _ = MetaDataSetter.ForImport(new3PPProduct, "GenericImporter-Method", "Created at import from Custom Descriptions.");
                            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(100);     //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
                        _ = MetaDataSetter.ForImport(CD, "GenericImporter-Method", "Initial import by CSV Importer (Function GenericImporter.ImportCustomDescriptions)");
                        
                        //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;
            }
        }
        
        private static bool ImportProducts(Dictionary<string, int> mappingTable)
        {
            List<Product> ProductsReadFromFile = new(ParseProductFile(mappingTable));
            return InsertProducts(ProductsReadFromFile);
        }

        private static bool InsertProducts(List<Product> products)
        {
            using (GremlinContext db = new())
            {
                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);
                    _ = MetaDataSetter.ForImport(product, "GenericImporter-Method");
                }

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

        private static List<Product> ParseProductFile(Dictionary<string, int> columnNumberOf)
        ///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);
            using (_csvParser)
            {
                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[columnNumberOf["ProductNumber"]];

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

                    ImportedProduct.SapShortDescription = fields[columnNumberOf["SapShortDescription"]];

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

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

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

                    ImportedProduct.ProductLine.ProductLineCode = fields[columnNumberOf["ProductLineCode"]];

                    switch (fields[columnNumberOf["ProductStatus"]])
                    {
                        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[columnNumberOf["Weight"]] != null) ImportedProduct.Weight = ParseWeight(fields[columnNumberOf["Weight"]]);
                    if (fields[columnNumberOf["WeightUnit"]] == "G") ImportedProduct.Weight /= 1000; //Umrechnung g in kg

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

                    ImportedProduct.SapLongDescription = fields[columnNumberOf["SapLongDescription"]];

                    results.Add(ImportedProduct);
                }
            }

            return results;
        }

        private 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;
            }

        }

        private static bool ImportLSAG(Dictionary<string, int> mappingTable)
        {
            bool result;
            result = ImportAccounts(mappingTable);
            ResetParser();
            _ = _csvParser.ReadFields();  //Skip Heading
            result = result && ImportContacts(mappingTable);
            return result;
        }

        public static bool ImportContacts(Dictionary<string, int> columnNumberOf)
        {
            List<Contact> ContactsReadFromFile = new(8000);
            using (_csvParser)
            {
                while (!_csvParser.EndOfData)
                {
                    Contact ImportedContact = new();
                    string[] fields = _csvParser.ReadFields();

                    //No conversion
                    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"]] switch
                    {
                        "Opt In" => true,
                        "Opt Out" => false,
                        _ => null,
                    };

                    //Convert "SAP Contact Number"
                    try
                    {
                        ImportedContact.SAPContactNumber = Convert.ToInt32(fields[columnNumberOf["SAPContactNumber"]], 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[columnNumberOf["SAPContactNumber"]];
                        if (errorRaiser == "") errorRaiser = "No Contact Number in this row!";
                        DbHelper.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!";
                        DbHelper.DisplayErrorDetails(ex, errorRaiser);
                        return false;
                    }

                    //Convert "Account Created on"
                    int year = Convert.ToInt32(fields[columnNumberOf["SAPContactCreationDate"]].Substring(0, 4), CultureInfo.CurrentCulture);
                    int month = Convert.ToInt32(fields[columnNumberOf["SAPContactCreationDate"]].Substring(4, 2), CultureInfo.CurrentCulture);
                    int day = Convert.ToInt32(fields[columnNumberOf["SAPContactCreationDate"]].Substring(6, 2), CultureInfo.CurrentCulture);
                    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"]];

                    ContactsReadFromFile.Add(ImportedContact);
                }

                //Eingelesenen Account in DB schreiben:
                using (GremlinContext db = new())
                {
                    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 = DbHelper.ResolveAccountById(db, Convert.ToUInt32(contact.Notes));
                        contact.Notes = "";

                        _ = MetaDataSetter.ForImport(contact, "GenericImporter-Method");
                    }

                    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 ImportAccounts(Dictionary<string, int> columnNumberOf)
        ///Importiert Account Daten aus CSV (erstellt aus LSAG_Contact_List_Tool.xlsx)
        ///
        ///     - Encoding: Latin1/ISO-8859-1
        ///     - 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'.
        ///
        ///Argumente: 
        ///  1. 

        {
            List<Account> AccountsReadFromFile = new(1000);

            while (!_csvParser.EndOfData)
            {
                bool DataHasError = false;

                // 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[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["SAPAccountNumber"]];
                    if (errorRaiser == "") errorRaiser = "No Account Number in this row!";
                    DbHelper.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["SAPAccountNumber"]];
                    if (errorRaiser == "") errorRaiser = "No Account Number in this row!";
                    DbHelper.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["ZIP"]];
                    if (errorRaiser == "") errorRaiser = "No ZIP in this row!";
                    DbHelper.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["ZIP"]];
                    if (errorRaiser == "") errorRaiser = "No ZIP in this row!";
                    DbHelper.DisplayErrorDetails(ex, errorRaiser);
                    return false;
                }

                //Konvertierung für AccountCreatedinSAPOn Property:
                //Test auf Inhalt bzw. Eintrag in der Mapping-Table (optionales Feld)
                if (columnNumberOf.TryGetValue("AccountCreatedinSAPOn", out int columnNumber))
                {
                    if (fields[columnNumberOf["AccountCreatedinSAPOn"]].Length != 0)
                    {
                        int year = Convert.ToInt32(fields[columnNumber].Substring(0, 4));
                        int month = Convert.ToInt32(fields[columnNumber].Substring(4, 2));
                        int day = Convert.ToInt32(fields[columnNumber].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["PhoneNumber"]];
                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);
            }                    

            //Eingelesenen Account in DB schreiben:
            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.
                AccountType accountType = db.AccountTypes
                .Where(a => a.AccountTypeCode == account.AccountType.AccountTypeCode)
                .First();

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

                account.SubMarket = subMarket;
                account.SubMarket = DbHelper.ResolveSubmarket(db, account.SubMarket.SubMarketCode);

                _ = MetaDataSetter.ForImport(account, "GenericImporter-Method");
                //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 ImportProductLine(TextFieldParser csvParser, Dictionary<string, int> mappingTable)
        {
            //foreach line in file:
            //read seed data, parse/split, save to object with metadata
            //add object to list
            //add list to context, savechanges

            List<ProductLine> productLinesReadFromFile = new(50);
            using (csvParser)
            {
               while (!csvParser.EndOfData)
                {
                    ProductLine importedProductLine = new();
                    string[] fields = csvParser.ReadFields();
                    importedProductLine.ProductLineCode = fields[mappingTable["ProductLineCode"]];
                    importedProductLine.ProductLineDescription = fields[mappingTable["ProductLineDescription"]];
                    productLinesReadFromFile.Add(importedProductLine);
                    _ = MetaDataSetter.ForImport(importedProductLine, "GenericImporter-Method");
                }
            }

            db.ProductLines.AddRange(productLinesReadFromFile);
            db.SaveChanges();
            
            return true;
        }

        public static bool ImportAccountType(TextFieldParser csvParser, Dictionary<string, int> mappingTable)
        {
            //foreach line in file:
            //read seed data, parse/split, save to object with metadata
            //add object to list
            //add list to context, savechanges

            List<AccountType> accountTypesReadFromFile = new(20);
            using (csvParser)
            {
                while (!csvParser.EndOfData)
                {
                    AccountType importedAccountType = new();
                    string[] fields = csvParser.ReadFields();
                    importedAccountType.AccountTypeCode = fields[mappingTable["AccountTypeCode"]];
                    importedAccountType.AccountTypeDescription = fields[mappingTable["AccountTypeDescription"]];
                    accountTypesReadFromFile.Add(importedAccountType);
                    _ = MetaDataSetter.ForImport(importedAccountType, "GenericImporter-Method");
                }
            }

            db.AccountTypes.AddRange(accountTypesReadFromFile);
            db.SaveChanges();
            
            return true;
        }

        public static bool ImportSubMarket(TextFieldParser csvParser, Dictionary<string, int> mappingTable)
        {
            //foreach line in file:
            //read seed data, parse/split, save to object with metadata
            //add object to list
            //add list to context, savechanges

            List<SubMarket> subMarketsReadFromFile = new(20);
            using (csvParser)
            {
               while (!csvParser.EndOfData)
                {
                    SubMarket importedSubMarket = new();
                    string[] fields = csvParser.ReadFields();
                    importedSubMarket.SubMarketCode = fields[mappingTable["SubMarketCode"]];
                    importedSubMarket.SubMarketDescription = fields[mappingTable["SubMarketDescription"]];
                    subMarketsReadFromFile.Add(importedSubMarket);
                    _ = MetaDataSetter.ForImport(importedSubMarket, "GenericImporter-Method");
                }
            }

            db.SubMarkets.AddRange(subMarketsReadFromFile);
            db.SaveChanges();
            
            return true;
        }

        private static void SetProperty(object gremlinEntity, PropertyInfo propertyInfo, string value)
        {
            propertyInfo.SetValue(gremlinEntity, value);
        }

        private static Array GetNewGremlinTypeArray(Assembly assembly, string detectedDataType, int length)
        {
            Type GremlinType = GetNewGremlinType(assembly, detectedDataType).GetType();
            //Create an one-dimensional array with n+1 or n+2 elements (n = number of datasets, plus heading and/or emptyline at EOF):
            return Array.CreateInstance(GremlinType, length);


            //Das geht alles nicht:
            //List<GremlinType> test = new();
            //List<typeof(GremlinType)> test = new();
            //List<GremlinType.GetType()> test = new();

            ////Das geht:
            ////Create iList:
            //Type listType = typeof(List<>).MakeGenericType(GremlinType);
            //var list = (IList)Activator.CreateInstance(listType);
            //list.Add(new Account() { AccountName = "Test" });
            //list.Add(GremlinClassObject);

        }

        private static object GetNewGremlinType(Assembly Gremlin, string detectedDataType)
        {
            return Activator.CreateInstance(Gremlin.ToString(), "Gremlin." + detectedDataType).Unwrap();
        }

        public static Dictionary<string, string> ReadMappingDictionaryFromFile()
        {
            Dictionary<string, string> result = new();
            string fileInput = FileIO.ReadResource("MappingDictionary.txt");
            string[] lines = fileInput.Split(Environment.NewLine);
            foreach (string line in lines)
            {
                string[] fields;
                fields = line.Split("|");
                result.Add(fields[0], fields[1]);
            }
            return result;
        }

        public static Dictionary<string, int> MapDataHeading(string[] headings, Dictionary<string, string> columnPropertyMapping)
        {
            Dictionary<string, int> result = new();
            for (int i = 0; i < headings.Length; i++)
            {
                string heading = headings[i].ToLower(CultureInfo.CurrentCulture)
                           .Trim()
                           .Replace(" ", "")
                           .Replace("-", "")
                           .Replace("_", "")
                           .Replace(".", "")
                           .Replace(":", "");

                if (columnPropertyMapping.TryGetValue(heading, out string value))
                {
                    result.Add(value, i);
                }
            }
            return result;
        }

        private static void BasimodosCodeDump()
        {
            //Unfertiger, generischer Code aus der ImportFile() Methode.
            //Aufbewahren für später, wenn Zeit ist, das ordentlich zu machen.
            
            
            //while (!csvParser.EndOfData)
            //{
            //    //read
            //    //new data instance
            //    //convert
            //    //validate
            //    //set metadata: SetMetadataForImport(IMetadata)
            //    //add to list

            //    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.


            //    //TO DO: vor der foreach "Account" bevorzugt behandeln (Sonderfall bei LSAG: Liste deduplizieren, und als erstes importieren, da sonst keine Contacts importiert werden können).


            //    foreach (string detectedDataType in detectedDataTypes)
            //    {
            //        //"Account" überspringen, da das vor der foreach schon verarbeitet worden ist.
            //        if (detectedDataType == "Account") continue;

            //        Array GremlinTypeArray = GetNewGremlinTypeArray(Gremlin, detectedDataType, numberOfLines);
            //        Type GremlinType = GetNewGremlinType(Gremlin, detectedDataType).GetType();
            //        PropertyInfo[] GremlinTypeProperties = GremlinType.GetProperties();

            //        foreach (PropertyInfo property in GremlinTypeProperties)
            //        {
            //            if (mappingTable.TryGetValue(property.Name, out int columnNumber))
            //            {
            //                //convert:
            //                GremlinTypeConverter.Convert(fields[columnNumber]);
            //            }
            //        }
            //    }

            //}

            //using (GremlinContext db = new())
            //{
            //    //add to context
            //    //savechanges
            //}
        }



        //#########################################################################################
        //Kann gelöscht werden, sobald die generische Funktion zuverlässig funktioniert:
        //#########################################################################################
        private static string RecognizeDataStatic(Dictionary<string, int> mappingDictionary)
        {
            //Logik zur Kategorisierung des Datensatzes:
            //Alle verpflichtenden Angaben zu einer Klasse vorhanen?
            //Alle vom Importer erwarteten Angaben vorhanden?
            //Bei den Enums zusätzlich noch proüfem, dass nur zwei Spalten vorhanden sind, sonst könnten LSAG-Daten falsch identifiziert werden.
            //
            //

            //Products
            if (//required by DB:
                mappingDictionary.ContainsKey("ProductNumber")
                && mappingDictionary.ContainsKey("ListPrice")
                //required by importer:
                && mappingDictionary.ContainsKey("Weight")
                //unique identifier:
                && mappingDictionary.ContainsKey("BreakRangeFrom")
                )
            {
                return "Product";
            }

            //LSAG Contact List Tool List
            if (//required by DB:
                mappingDictionary.ContainsKey("SAPAccountNumber")
                && mappingDictionary.ContainsKey("SAPContactNumber")
                && mappingDictionary.ContainsKey("AccountTypeCode")
                && mappingDictionary.ContainsKey("SubMarketCode")
                && mappingDictionary.ContainsKey("LastName")
                && mappingDictionary.ContainsKey("AccountName")
                //required by importer:

                //unique identifier:
                && mappingDictionary.ContainsKey("MA_ProductInterests")
                )
            {
                return "LSAG Contact List Tool List";
            }

            //Accounts
            if (//required by DB:
                mappingDictionary.ContainsKey("AccountName")
                && mappingDictionary.ContainsKey("Street")
                && mappingDictionary.ContainsKey("ZIP")
                && mappingDictionary.ContainsKey("City")
                && mappingDictionary.ContainsKey("PhoneNumber")
                && mappingDictionary.ContainsKey("SAPAccountNumber")
                && mappingDictionary.ContainsKey("AccountCreatedInSAPOn")
                && mappingDictionary.ContainsKey("AccountTypeCode")
                && mappingDictionary.ContainsKey("SubMarketCode")
                //required by importer:
                )
            {
                return "Account";
            }

            //Contacts
            if (//required by DB:
                mappingDictionary.ContainsKey("LastName")
                && mappingDictionary.ContainsKey("SAPContactNumber")
                //required by importer:
                && mappingDictionary.ContainsKey("FirstName")
                && mappingDictionary.ContainsKey("Gender")
                )
            {
                return "Contact";
            }

            //Custom Description
            if (//required by DB:
                mappingDictionary.ContainsKey("Heading")
                //required by importer: 
                && mappingDictionary.ContainsKey("ProductNumber")
                && mappingDictionary.ContainsKey("OptionNumber")
                && mappingDictionary.ContainsKey("DescriptionText")
                //unique identifier:
                && mappingDictionary.ContainsKey("CoverletterText")
                )
            {
                return "CustomDescription";
            }


            //Pseudo-Enums AccountTypes, SubMarkets, ProductLines for DB-initializing
            if (mappingDictionary.Count == 2)
            {
                if (mappingDictionary.ContainsKey("AccountTypeCode")
                    && mappingDictionary.ContainsKey("AccountTypeDescription")
                    && mappingDictionary.Count == 2)
                {
                    return "AccountType";
                }

                if (mappingDictionary.ContainsKey("SubMarketCode")
                    && mappingDictionary.ContainsKey("SubMarketDescription")
                    && mappingDictionary.Count == 2)
                {
                    return "SubMarket";
                }

                if (mappingDictionary.ContainsKey("ProductLineCode")
                    && mappingDictionary.ContainsKey("ProductLineDescription")
                    && mappingDictionary.Count == 2)
                {
                    return "ProductLine";
                }
            }

            return "No entity type unambigiously identified!";

        }
    }
}
