using Serilog.Core;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using WK5.Core;
using WK5.Core.Email;
using WK5.Core.Models;
using WK5.Core.OpenCart.Karley;
using WK5.Core.Services;

namespace KarleyUpdate
{
    public struct AbweichendePreiseArtikel
    {
        public string Artikelnummer { get; set; }
        public decimal Max { get; set; }
        public decimal Min { get; set; }
    }

    public class Preiskalkulation
    {
        public const int LIEFERANTEN_PREIS_GÜLTIGKEITS_TAGE = 7;
        public List<KarleyProduct> OpencartProduct { get; set; } = new List<KarleyProduct>();
        public List<string> IgnorierteArtikelVKÜberEVK { get; set; } = new List<string>();

        private List<Artikel> _teureArtikel = new ();
        private readonly Logger _logger;

        public Preiskalkulation(Logger logger)
        {
            IgnorierteArtikelVKÜberEVK = GetIgnorierteArtikelÜberEVK();
            _logger = logger;
        }


        public async Task RunAsync()
        {
            using FbController2 fbController = new FbController2(2);

            await UpdateCurrencyAsync(fbController);

            await CheckLieferantenpreisAbweichungenAsync(fbController);

            await PreiseUpdateAsync(fbController);

            await CheckTeureArtikelAsync(fbController);

            await CheckVerkaufspreisIstKleinerAlsEinkaufspreisAsync(fbController);
        }
        /// <summary>
        /// Aktualisiert die Währungskurse
        /// </summary>
        /// <param name="fbController"></param>
        /// <returns></returns>
        private async Task UpdateCurrencyAsync(FbController2 fbController)
        {

            string url = "https://www.ecb.int/stats/eurofxref/eurofxref-daily.xml";
            DateTime UpdateDatum;// = DateTime.Now;

            XDocument doc = XDocument.Load(url);

            XNamespace gesmes = "http://www.gesmes.org/xml/2002-08-01";
            XNamespace ns = "http://www.ecb.int/vocabulary/2002-08-01/eurofxref";

#nullable disable
            var cubes = doc.Descendants(ns + "Cube")
                           .Where(x => x.Attribute("currency") != null)
                           .Select(x => new
                           {
                               Currency = (string)x.Attribute("currency"),
                               Rate = (decimal)x.Attribute("rate")
                           });
            var cubeTime = doc.Descendants(ns + "Cube")
                           .Where(x => x.Attribute("time") != null)
                           .Select(x => new
                           {
                               Datum = (String)x.Attribute("time")
                           });
            DateTime.TryParse(cubeTime.First().Datum, out UpdateDatum);

#nullable enable
            //UpdateDatum = cubeTime.First().Datum;


            foreach (var result in cubes)
            {
                if (result.Currency == "CHF" || result.Currency == "USD")
                {
                    double Waehrung1 = Convert.ToDouble(result.Rate);
                    double Waehrung2 = 1 / Waehrung1;
                    fbController.AddParameter("@Waehrung1", Convert.ToDecimal(Waehrung1));
                    fbController.AddParameter("@Waehrung2", Convert.ToDecimal(Waehrung2));
                    fbController.AddParameter("@Datum", UpdateDatum);
                    fbController.AddParameter("@WAEHZEICHEN", result.Currency);
                    await fbController.QueryAsync("UPDATE WAEHRUNG SET WAEH_N_KURS = @Waehrung1, WAEH_N_WECHSELKURS = @Waehrung2, WAEH_D_KURSVOM = @Datum, WAEH_TIMESTAMP = CURRENT_TIMESTAMP WHERE WAEH_A_ZEICHEN = @WAEHZEICHEN");
                    Console.WriteLine("{0}: {1}", result.Currency, result.Rate);
                }
            }
        }
        /// <summary>
        /// Prüft in den Lieferantenpreisen, ob es starke Abweichungen gibt
        /// </summary>
        /// <param name="fbController"></param>
        /// <returns></returns>
        private async Task CheckLieferantenpreisAbweichungenAsync(FbController2 fbController)
        {
            List<string> ignorierteArtikelLieferantenPreise = GetIgnorierteArtikelAbweichendeLieferantenPreise();

            StringBuilder abweichendeLieferantenPreiseMail = new StringBuilder();
            abweichendeLieferantenPreiseMail.Append("<h1>Es wurden starke Abweichungen bei den Einkaufspreisen festgestellt!</h1>");
            abweichendeLieferantenPreiseMail.Append("<p>Arbeitsanweisung: http://wikis.local/wiki/doku.php/wk5:stammdaten:artikel:artikelimport:meldungen:au0004</p>");
            abweichendeLieferantenPreiseMail.Append("<table><thead><tr><th>Artikelnummer</th><th>Min-Preis</th><th>Max-Preis</th></thead><tbody>");

            int countLieferantenPreise = 0;
            await foreach (AbweichendePreiseArtikel artikel in ArtikelMitAbweichendenLieferantenPreisen(fbController))
            {
                if (!ignorierteArtikelLieferantenPreise.Where(x => x.Trim().Equals(artikel.Artikelnummer, StringComparison.OrdinalIgnoreCase)).Any())
                {
                    abweichendeLieferantenPreiseMail.Append($"<tr>");
                    abweichendeLieferantenPreiseMail.Append($"<td><a href=\"http://wk5.local/Artikel/{artikel.Artikelnummer}\">{artikel.Artikelnummer}</a></td>");
                    abweichendeLieferantenPreiseMail.Append($"<td>{artikel.Min:C2}</td>");
                    abweichendeLieferantenPreiseMail.Append($"<td>{artikel.Max:C2}</td>");
                    abweichendeLieferantenPreiseMail.Append($"</tr>");
                    countLieferantenPreise++;
                }
            }

            abweichendeLieferantenPreiseMail.Append($"</tbody></table>");

            if (countLieferantenPreise > 0)
            {
                await EmailController.FehlerMailSendenAsync("Falsche Einkaufspreise", abweichendeLieferantenPreiseMail.ToString(), "shopverwaltung@karley.eu");
            }
        }
        /// <summary>
        /// Aktualisiert alle Einkaufs- und Verkaufspreise der Artikel
        /// </summary>
        /// <param name="fbController"></param>
        /// <returns></returns>
        private async Task PreiseUpdateAsync(FbController2 fbController)
        {
            string artikelSql = $"SELECT DISTINCT ARTI_A_NR FROM ARTIKEL WHERE ARTI_L_INAKTIV = 'N'";

            DataTable artikelData = await fbController.SelectDataAsync(artikelSql);

            ArtikelService artikelService = new ArtikelService();

            int counter = 0;
            foreach (DataRow row in artikelData.Rows)
            {
                Console.Write($"\r[{counter}/{artikelData.Rows.Count}]");
                string? artikelnummer = row.Field<string>("ARTI_A_NR");
                if (!String.IsNullOrWhiteSpace(artikelnummer))
                {
                    Artikel? arti = await artikelService.GetAsync(artikelnummer, fbController);
                    if (arti is not null)
                    {
                        decimal listenpreis = arti.ARTI_N_VK4;
                        decimal alterEK = arti.ARTI_N_EK;

                        // Ek berechnen
                        await SetzeEK(arti);

                        //VK berechnen
                        await SetzeVK(arti, alterEK);

                        //Staffelpreise automatisch berechnen??

                        //Teure Artikel filtern
                        if (IstZuTeuer(arti))
                        {
                            _teureArtikel.Add(arti);
                        }

                        // Änderungen speichern
                        await artikelService.UpdateAsync(arti, fbController);
                    }
                }
                counter++;
            }
            Console.Write("\r\n");
        }
        /// <summary>
        /// Prüft auf Artikel, die laut Lieferantenpreisen bei uns viel zu Teuer sind.
        /// </summary>
        /// <param name="fbController"></param>
        /// <returns></returns>
        private async Task CheckTeureArtikelAsync(FbController2 fbController)
        {
            if (_teureArtikel.Count > 0)
            {
                StringBuilder sb = new StringBuilder();

                sb.AppendLine("<table>");

                sb.AppendLine("\t<tr>");
                sb.AppendLine("\t\t<th>");
                sb.AppendLine("\t\t\tArtikelnummer");
                sb.AppendLine("\t\t</th>");

                sb.AppendLine("\t\t<th>");
                sb.AppendLine("\t\t\tVK");
                sb.AppendLine("\t\t</th>");

                sb.AppendLine("\t\t<th>");
                sb.AppendLine("\t\t\tUVP");
                sb.AppendLine("\t\t</th>");

                sb.AppendLine("\t\t<th>");
                sb.AppendLine("\t\t\tEK");
                sb.AppendLine("\t\t</th>");

                sb.AppendLine("\t\t<th>");
                sb.AppendLine("\t\t\tPreisgruppe");
                sb.AppendLine("\t\t</th>");

                sb.AppendLine("\t\t<th>");
                sb.AppendLine("\t\t\tHersteller");
                sb.AppendLine("\t\t</th>");

                sb.AppendLine("\t</tr>");
                foreach (Artikel artikel in _teureArtikel)
                {
                    sb.AppendLine("\t<tr>");
                    sb.AppendLine("\t\t<td>");
                    sb.AppendLine($"\t\t\t<a href='http://wk5.local/Artikel/{artikel.Artikelnummer}'>{artikel.Artikelnummer}</a>");
                    sb.AppendLine("\t\t</td>");

                    sb.AppendLine("\t\t<td>");
                    sb.AppendLine($"\t\t\t{artikel.ARTI_N_VK1}");
                    sb.AppendLine("\t\t</td>");

                    sb.AppendLine("\t\t<td>");
                    sb.AppendLine($"\t\t\t{artikel.ARTI_N_VK4}");
                    sb.AppendLine("\t\t</td>");

                    sb.AppendLine("\t\t<td>");
                    sb.AppendLine($"\t\t\t{artikel.ARTI_N_EK}");
                    sb.AppendLine("\t\t</td>");

                    sb.AppendLine("\t\t<td>");
                    sb.AppendLine($"\t\t\t{artikel.Preisgruppe}");
                    sb.AppendLine("\t\t</td>");

                    sb.AppendLine("\t\t<td>");
                    sb.AppendLine($"\t\t\t{artikel.Hersteller}");
                    sb.AppendLine("\t\t</td>");
                    sb.AppendLine("\t</tr>");
                }
                sb.AppendLine("</table>");




                StringBuilder teureArtikelMail = new StringBuilder();
                teureArtikelMail.Append($"<h1>Es wurden {_teureArtikel.Count} Artikel mit zu hohem Preis gefunden</h1>");
                teureArtikelMail.Append($"<p>Unser VK ist größer als der UVP/Listenpreis</p><br/>");
                teureArtikelMail.Append($"<p>Um Artikel dauerhaft zu ignorieren in die IgnoreArtikelWeilVkUeberEVK.txt eintragen</p><br/>");
                teureArtikelMail.Append($"<p>Arbeitsanweisung: http://wikis.local/wiki/doku.php/wk5:stammdaten:artikel:artikelimport:meldungen:au0005</p><br/>");

                teureArtikelMail.AppendLine(sb.ToString());



                await EmailController.FehlerMailSendenAsync($"{_teureArtikel.Count} Artikel mit zu hohem Preis", teureArtikelMail.ToString(), "shopverwaltung@karley.eu");
            }
        }
        /// <summary>
        /// Prüft auf Artikel, die wir günstiger verkaufen als einkaufen.
        /// </summary>
        /// <param name="fbController"></param>
        /// <returns></returns>
        private async Task CheckVerkaufspreisIstKleinerAlsEinkaufspreisAsync(FbController2 fbController)
        {
            List<string> ignorierteArtikelEkTeurerVk = GetIgnorierteArtikelEKTeurerVK();

            string artikelEkNiedrigerVKSql = $@"SELECT ARTI_A_NR,ARTI_N_EK,ARTI_N_VK1 
FROM ARTIKEL 
WHERE ((ARTI_N_VK1 < ARTI_N_EK AND ARTI_N_VK1 > 0)
OR (ARTI_N_VK2 < ARTI_N_EK AND ARTI_N_VK2 > 0)
OR (ARTI_N_VK5 < ARTI_N_EK AND ARTI_N_VK5 > 0))
AND ARTI_L_INAKTIV = 'N' 
AND ARTI_L_ABVERKAUF = 'N' 
AND ARTI_A_NR NOT IN ('{String.Join("','", ignorierteArtikelEkTeurerVk)}')";


            DataTable ignorierteArtikelEkTeurerVkData = await fbController.SelectDataAsync(artikelEkNiedrigerVKSql);

            if (ignorierteArtikelEkTeurerVkData.Rows.Count > 0)
            {
                StringBuilder ekTeurerVkMail = new StringBuilder();
                ekTeurerVkMail.Append("<h1>Es wurden Artikel mit zu niedrigem VK gefunden</h1>");
                ekTeurerVkMail.Append("<p>Der VK ist geringer als der EK</p><br/>");
                ekTeurerVkMail.Append($"<p>Um Artikel dauerhaft zu ignorieren in die IgnoreEKTeurerAlsVK.txt eintragen</p><br/>");
                ekTeurerVkMail.Append($"<br/>");
                ekTeurerVkMail.Append($"<table><thead><tr><th>Artikel</th><th>EK</th><th>VK</th></tr></thead><tbody>");

                foreach (DataRow row in ignorierteArtikelEkTeurerVkData.Rows)
                {
                    string artikelnummer = row.Field<string>("ARTI_A_NR") ?? String.Empty;
                    decimal ek = row.Field<decimal>("ARTI_N_EK");
                    decimal vk = row.Field<decimal>("ARTI_N_VK1");


                    ekTeurerVkMail.Append($"<tr>");
                    ekTeurerVkMail.Append($"<td><a href=\"http://wk5.local/Artikel/{artikelnummer}\">{artikelnummer}</a></td>");
                    ekTeurerVkMail.Append($"<td>{ek.ToString("C2")}</td>");
                    ekTeurerVkMail.Append($"<td>{vk.ToString("C2")}</td>");
                    ekTeurerVkMail.Append($"</tr>");
                }

                ekTeurerVkMail.Append($"</tbody></table>");

                await EmailController.FehlerMailSendenAsync("Artikel mit zu niedrigem VK", ekTeurerVkMail.ToString(), "shopverwaltung@karley.eu");

            }
        }
        private static List<string> ReadList(string file)
        {
            List<string> retval = new List<string>();


            var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            using (StreamReader r = new StreamReader(fs, Encoding.Default))
            {
                string? line;
                while ((line = r.ReadLine()) != null)
                {
                    retval.Add(line);
                }
            }

            return retval;
        }

        private static List<string> GetIgnorierteArtikelAbweichendeLieferantenPreise()
        {
#if DEBUG
            string file = Path.Combine(GlobalConfig.W4TmpFolderSrv01, $"IgnoreEinkaufsDifferenzen.txt");
#else
            string file = Path.Combine(GlobalConfig.W4TmpFolder, $"IgnoreEinkaufsDifferenzen.txt");
#endif
            return ReadList(file);
        }

        private static List<string> GetIgnorierteArtikelÜberEVK()
        {
#if DEBUG
            string file = Path.Combine(GlobalConfig.W4TmpFolderSrv01, $"IgnoreArtikelWeilVkUeberEVK.txt");
#else
            string file = Path.Combine(GlobalConfig.W4TmpFolder, $"IgnoreArtikelWeilVkUeberEVK.txt");
#endif
            return ReadList(file);
        }

        private static List<string> GetIgnorierteArtikelEKTeurerVK()
        {
#if DEBUG
            string file = Path.Combine(GlobalConfig.W4TmpFolderSrv01, $"IgnoreEKTeurerAlsVK.txt");
#else
            string file = Path.Combine(GlobalConfig.W4TmpFolder, $"IgnoreEKTeurerAlsVK.txt");
#endif
            return ReadList(file);
        }

        private async IAsyncEnumerable<AbweichendePreiseArtikel> ArtikelMitAbweichendenLieferantenPreisen(FbController2 fbController)
        {
            //JS 07.07.2022: Ley sagt Lieferantenbestand soll ignoriert werden
            // AND WK5_ARLI_N_LIEFBESTAND > 0
            // JS 25.07.2022: Ley sagt es sollen nur Artikel beachtet werden, bei denen mindestens ein Lieferant Bestand > 0 vorhanden ist

            string sql = @"SELECT ARLI_A_ARTIKELNR,MAX(ARLI_N_PREIS) as maxPreis, MIN(ARLI_N_PREIS) as minPreis
FROM LIEFERANTENARTIKEL LEFT JOIN ARTIKEL ON ARLI_A_ARTIKELNR = ARTI_A_NR
WHERE ARTI_L_FIXPREIS = 'N'
AND ARTI_L_INAKTIV = 'N'
AND DATEDIFF(DAY FROM ARLI_D_LETZAEND TO CURRENT_TIMESTAMP) < 30
GROUP BY ARLI_A_ARTIKELNR
HAVING
(COUNT(ARLI_A_ARTIKELNR) > 1 AND SUM(WK5_ARLI_N_LIEFBESTAND) > 0)
AND
(
MAX(ARLI_N_PREIS) > 0
AND MIN(ARLI_N_PREIS) > 0
AND((MIN(ARLI_N_PREIS) - MAX(ARLI_N_PREIS)) / MAX(ARLI_N_PREIS)) * -100 > 30
)";
            DataTable data = await fbController.SelectDataAsync(sql);

            foreach (DataRow row in data.Rows)
            {
                string? artikelnummer = row.Field<string>("ARLI_A_ARTIKELNR");
                if (!String.IsNullOrWhiteSpace(artikelnummer))
                {
                    AbweichendePreiseArtikel artikel = new AbweichendePreiseArtikel
                    {

                        Artikelnummer = artikelnummer,
                        Max = row.Field<decimal>("maxPreis"),
                        Min = row.Field<decimal>("minPreis")

                    };

                    // Wenn Preisdifferenz kleiner als 7€, wollen wir den nicht drin haben
                    if (artikel.Max - artikel.Min >= 7)
                    {
                        yield return artikel;
                    }
                }
            }

        }

        public async Task SetzeEK(Artikel artikel)
        {
            artikel.ARTI_N_EK = await artikel.GetEinkaufspreis();
        }

        public bool IstZuTeuer(Artikel artikel)
        {
            decimal uvp = artikel.ARTI_N_VK4;
            if (uvp > 0 && uvp > artikel.ARTI_N_EK)
            {
                if (artikel.ARTI_N_VK1 > uvp && 
                    !artikel.ARTI_L_INAKTIV && 
                    artikel.ARTI_N_EK > 60 
                    && !IgnorierteArtikelVKÜberEVK.Where(x => x.Trim().Equals(artikel.Artikelnummer, StringComparison.OrdinalIgnoreCase)).Any())
                {
                    return true;
                }
            }
            return false;
        }

        public async Task SetzeVK(Artikel artikel, decimal alterEK)
        {
            decimal ek = await artikel.GetEinkaufspreis();

            if (artikel.Bestand > 0)
            {
                ek = alterEK;
            }



            if (!artikel.ARTI_L_FIXPREIS)
            {
                Preisgruppe? preisgruppe = await Preisgruppe.GetPreisgruppeAsync(artikel.Preisgruppe);
                if (preisgruppe is not null)
                {
                    artikel.ARTI_N_BRUTTO = preisgruppe.GetAbschlagVK(ek, 1);
                    artikel.ARTI_N_VK1 = preisgruppe.GetAbschlagVK(ek, 2);
                    artikel.ARTI_N_VK2 = preisgruppe.GetAbschlagVK(ek, 3);
                    artikel.ARTI_N_VK3 = preisgruppe.GetAbschlagVK(ek, 4);
                    artikel.ARTI_N_VK4 = preisgruppe.GetAbschlagVK(ek, 5);
                    artikel.ARTI_N_VK5 = preisgruppe.GetAbschlagVK(ek, 6);

                    artikel.ARTI_N_VK1BRUTTO = Math.Round(artikel.ARTI_N_VK1 * (1 + (artikel.MWST_N_PROZENT / 100)), 2);
                }
            }
        }



    }
}
