using KarleyLibrary.Erweiterungen;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;

namespace WK5.Core.Models.Bestellungen
{
    public class AuftragBestellenController
    {
        //public static readonly int[] LieferantenMitUnbegrenztenLagerbestand = new int[] { 71063, 70002, 71088 };

        public static readonly int[] LieferantenMitUnbegrenztenLagerbestand = Array.Empty<int>();

        /// <summary>
        /// Ruft den Auftrag ab, für den Bestellungen aufgenommen werden sollen.
        /// </summary>
        public Auftrag Auftrag { get; }
        /// <summary>
        /// Ruft einen Wert ab, der angibt, ob für den Auftrag eine Bestellung aufgenommen werden darf, oder nicht.
        /// <para>Laut Regelsatz darf nur dann eine Bestellung für einen Auftrag aufgenommen werden, wenn es für diesen noch keine Bestellungen gibt.</para>
        /// </summary>
        public bool BestellenErlaubt { get; }
        /// <summary>
        /// Ruft einen Wert ab, der angibt, ob noch eine weitere Bestellung hinzugefügt werden darf, oder nicht.
        /// <para>
        /// Liefert true, wenn weniger Bestllungen als Lieferanten und nicht alle Artikel in den Bestellungen enthalten sind.
        /// </para>
        /// </summary>
        public bool CanAddBestellung
        {
            get
            {
                var artikelInBestellungen = Bestellungen.SelectMany(x => x.Positionen).ToArray();
                return Bestellungen.Count < Lieferanten.Count && artikelInBestellungen.Length < Artikel.Count;
            }
        }
        /// <summary>
        /// Ruft die Bestellungen ab, die vom Controller angelegt werden sollen.
        /// </summary>
        public List<AuftragBestellenBestellung> Bestellungen { get; }
        /// <summary>
        /// Ruft alle Artikel ab, die für einen Auftrag bestellt werden können.
        /// </summary>
        public List<AuftragBestellenArtikel> Artikel { get; }
        /// <summary>
        /// Ruft alle Artikel des Auftrags ab, die nicht bestellt werden können, da kein Lieferant gefunden wurde, der ausreichend Lagerbestand hat, oder eine zu hohe Mindestbestellmenge hat oder der Preis zu lange nicht upgedatet wurde.
        /// </summary>
        public List<AuftragBestellenArtikel> ArtikelNichtVerfügbar { get; }

        /// <summary>
        /// Liefert eine Zuordnung von einem Artikel zu allen möglichen Lieferantenartikeln dar.
        /// </summary>
        public Dictionary<AuftragBestellenArtikel, AuftragBestellenLieferantArtikel[]> ArtikelZuLieferantenArtikel { get; } = new Dictionary<AuftragBestellenArtikel, AuftragBestellenLieferantArtikel[]>();

        /// <summary>
        /// Liefert alle Lieferanten, bei denen wir noch eine Bestellung aufnehmen können.
        /// </summary>
        public List<AuftragBestellenLieferant> Lieferanten { get; } = new List<AuftragBestellenLieferant>();

        /// <summary>
        /// Liefert alle verfügbaren Lieferanten für einen Artikel
        /// </summary>
        /// <param name="artikel"></param>
        /// <returns></returns>
        public IEnumerable<AuftragBestellenLieferant> GetLieferantenFürArtikel(AuftragBestellenArtikel artikel)
        {
            if (ArtikelZuLieferantenArtikel.ContainsKey(artikel))
            {
                foreach (var item in ArtikelZuLieferantenArtikel[artikel])
                {
                    yield return item.Lieferant;
                }
            }
        }

        /// <summary>
        /// Liefert den günstigsten <see cref="AuftragBestellenLieferantArtikel"/> für einen Artikel, welcher ausreichend Lagerbestand hat.
        /// </summary>
        /// <param name="artikel"></param>
        /// <returns></returns>
        public AuftragBestellenLieferantArtikel? GetGünstigstenLieferantenArtikel(AuftragBestellenArtikel artikel)
        {
            if (!ArtikelZuLieferantenArtikel.ContainsKey(artikel))
            {
                return null;
            }

            var sucheArtikel = ArtikelZuLieferantenArtikel[artikel];

            // Suche den günstigsten Lieferanten
            // Bong, Veit und DTM haben immer Lagerbestand
            int[] lieferanten = new int[] { 71063, 70002, 71088 };

            var sucheLieferantenArtikel =
                ArtikelNichtVerfügbar.Contains(artikel)
                ? sucheArtikel?.OrderBy(x => x.PreisMitRabatt(artikel.Menge)).FirstOrDefault()
                : sucheArtikel?.Where(x => (x.Bestand >= artikel.Menge || lieferanten.Contains(x.Lieferant.LieferantId)) && artikel.Menge >= x.Mindestabnahme).OrderBy(x => x.PreisMitRabatt(artikel.Menge)).FirstOrDefault();

            return sucheLieferantenArtikel;
        }

        public AuftragBestellenLieferantArtikel? GetHauptLieferantenArtikel(AuftragBestellenArtikel artikel)
        {
            if (!ArtikelZuLieferantenArtikel.ContainsKey(artikel))
            {
                return null;
            }

            var sucheArtikel = ArtikelZuLieferantenArtikel[artikel];

            // Suche den günstigsten Lieferanten
            // Bong, Veit und DTM haben immer Lagerbestand
            int[] lieferanten = new int[] { 71063, 70002, 71088 };

            var sucheLieferantenArtikel = sucheArtikel?.Where(x => x.IstHauptlieferant).FirstOrDefault();

            return sucheLieferantenArtikel;
        }

        /// <summary>
        /// Liefert den Lieferantenartikel für den angebenen Artikel mit Lieferanten.
        /// </summary>
        /// <param name="artikel"></param>
        /// <param name="lieferantenId">Die ID des Lieferanten, dessen LieferantenArtikel gefunden werden soll.</param>
        /// <returns></returns>
        public AuftragBestellenLieferantArtikel? GetLieferantenArtikel(AuftragBestellenArtikel artikel, int lieferantenId)
        {
            if (!ArtikelZuLieferantenArtikel.ContainsKey(artikel))
            {
                return null;
            }

            var sucheArtikel = ArtikelZuLieferantenArtikel[artikel];

            // Bong, Veit und DTM haben immer Lagerbestand

            var sucheLieferantenArtikel = sucheArtikel?.Where(x => x.Lieferant.LieferantId == lieferantenId).OrderBy(x => x.PreisMitRabatt(artikel.Menge)).FirstOrDefault();
            return sucheLieferantenArtikel;
        }

        public bool IstLieferbarDurchLieferanten(AuftragBestellenArtikel artikel, int lieferantenId)
        {
            var lieferantenArtikel = GetLieferantenArtikel(artikel, lieferantenId);

            if (lieferantenArtikel is null)
            {
                return false;
            }

            return LieferantenMitUnbegrenztenLagerbestand.Contains(lieferantenId) || lieferantenArtikel.Bestand >= artikel.Menge;
        }

        /// <summary>
        /// Liefert alle Lieferanten zurück, die für eine Bestellung verfügbar sind.
        /// <para>
        /// Die Verfügbaren Lieferanten ergeben sich aus allen Lieferanten der ersten Position.
        /// </para>
        /// </summary>
        /// <param name="bestellung"></param>
        /// <returns></returns>
        public IEnumerable<AuftragBestellenLieferant> GetLieferantenFürBestellung(AuftragBestellenBestellung bestellung)
        {
            if (bestellung.Positionen.Count > 0)
            {
                var artikel = bestellung.Positionen[0].SelectedArtikel;
                // Keine Lieferanten verfügbar machen, die bereits in einer anderen Bestellung enthalten sind.
                var tmp = Bestellungen.Where(x => x != bestellung && x.Lieferant is not null).Select(x => x.Lieferant).ToArray();
                foreach (var lieferant in GetLieferantenFürArtikel(artikel).Where(x => !tmp.Contains(x)))
                {
                    yield return lieferant;
                }
            }

            // Wenn es keine Positionen gibt, dann gibt es auch keine Lieferanten. Die ergeben sich immer aus der ersten Position einer Bestellung.
        }
        /// <summary>
        /// Liefert alle Artikel zurück, die der übergebenen Bestellung noch zugeordnet werden können.
        /// </summary>
        /// <param name="bestellung"></param>
        /// <returns></returns>
        public IEnumerable<AuftragBestellenArtikel> GetArtikelFürBestellung(AuftragBestellenBestellung bestellung)
        {
            // Wenn es noch keinen Lieferanten für die Bestellung gibt, dann brauchen wir alle Artikel, die noch bestellt werden können
            if (bestellung.Lieferant is null)
            {
                var artikelInBestellungen = Bestellungen.SelectMany(x => x.Positionen.Select(y => y.SelectedArtikel)).ToArray();
                foreach (var artikel in Artikel.Where(x => !artikelInBestellungen.Contains(x)))
                {
                    yield return artikel;
                }
            }
            else
            {
                // Ansonsten brauchen wir nur die Artikel, die wir von dem Lieferanten der Bestellung beziehen können
                var artikelVonBestellung = bestellung.Positionen.Select(x => x.SelectedArtikel).ToArray();
                foreach (var artikel in ArtikelZuLieferantenArtikel
                    .Where(x => !artikelVonBestellung.Contains(x.Key)
                    && x.Value.Where(y => y.Lieferant.LieferantId == bestellung.Lieferant.LieferantId).Any())
                    .Select(x => x.Key))
                {
                    yield return artikel;
                }
            }
        }
        /// <summary>
        /// Löscht alle ausgewählten Artikelnummern in allen Bestellungen.
        /// </summary>
        public void ClearArtikelnummernInBestellungen()
        {
            foreach (var bestellung in Bestellungen)
            {
                bestellung.InputArtikelnummer = null;
            }
        }

        public (bool überschritten, decimal mindestbestellmenge, bool nurVielfaches) MindestbestellmengeUnterschritten(decimal menge, int lieferantId, AuftragBestellenArtikel artikel)
        {
            var sucheLieferantArtikel = ArtikelZuLieferantenArtikel[artikel].Where(x => x.Lieferant.LieferantId == lieferantId).FirstOrDefault();

            if (sucheLieferantArtikel is null)
            {
                throw new ArgumentNullException(nameof(sucheLieferantArtikel));
            }

            if (sucheLieferantArtikel.Mindestabnahme > 1)
            {
                return (menge < sucheLieferantArtikel.Mindestabnahme, sucheLieferantArtikel.Mindestabnahme, sucheLieferantArtikel.NurVielfaches);
            }

            return (false, 1, true);
        }
        public AuftragBestellenController(Auftrag auftrag, bool bestellenErlaubt)
        {
            Auftrag = auftrag;
            BestellenErlaubt = bestellenErlaubt;
            Bestellungen = new List<AuftragBestellenBestellung>();

            Artikel = new List<AuftragBestellenArtikel>();
            ArtikelNichtVerfügbar = new List<AuftragBestellenArtikel>();

            // Was brauche ich? Artikel, Lieferanten und Lieferantenartikel
            foreach (var position in Auftrag.GetEndPositionen().Where(x => x.ARTI_L_LAGERFUEHR))
            {
                // Es kann sein, dass ein Artikel als zwei Positionen im Auftrag enthalten ist, daher müssen wir prüfen, ob wir den Artikel bereits abgefragt haben, wenn ja, dann summieren wir einfach die Mengen auf
                var sucheArtikel = Artikel.Where(x => x.Artikelnummer.Equals(position.Artikelnummer));
                if (sucheArtikel.Any())
                {
                    foreach (var artikel in sucheArtikel)
                    {
                        artikel.Menge += position.Menge;
                    }
                }
                else
                {
                    using FbController2 fbController = new FbController2();
                    fbController.AddParameter("@ARLI_A_ARTIKELNR", position.Artikelnummer);

                    var data = fbController.SelectData(@"SELECT ARLI_N_LIEFNR, ARLI_A_BESTELLNR, ARLI_L_HAUPTLIEFERANT,
ARLI_N_PREIS, ARLI_N_RABATT, ARLI_N_RABATT2, ARLI_N_MINDESTABNAHME, ARLI_B_NOTIZ,
ARLI_N_MENGE1, ARLI_N_MENGE2, ARLI_N_MENGE3, ARLI_N_MENGE4,
ARLI_N_PREISMENGE1, ARLI_N_PREISMENGE2, ARLI_N_PREISMENGE3, ARLI_N_PREISMENGE4,
ARLI_N_RABATTMENGE1, ARLI_N_RABATTMENGE2, ARLI_N_RABATTMENGE3, ARLI_N_RABATTMENGE4,
ARLI_L_NURVIELFACHES,
LIEF_A_NAME1, LIEF_WK5_L_DIREKT_ERLAUBT, LIEF_N_ZABD, LIEF_N_LIBD, LIEF_A_WAEHRUNG,
LIEF_WK5_N_MINDERMENGENZUSCHLAG, LIEF_N_MIDBESTWERT, LIEF_A_LAND,
WK5_ARLI_N_LIEFBESTAND AS LIEFERANTEN_BESTAND,
ARTI_A_BEZ1 AS ARLI_A_BEZ1, 
ARTI_A_BEZ2 AS ARLI_A_BEZ2, 
ARTI_A_BEZ3 AS ARLI_A_BEZ3, 
ARTI_A_BEZ4 AS ARLI_A_BEZ4, 
ARTI_A_BEZ5 AS ARLI_A_BEZ5
FROM LIEFERANTENARTIKEL
LEFT JOIN LIEFERANTEN ON LIEF_N_NR = ARLI_N_LIEFNR 
LEFT JOIN ARTIKEL ON ARTI_A_NR = ARLI_A_ARTIKELNR
WHERE ARLI_A_ARTIKELNR = @ARLI_A_ARTIKELNR");

                    List<AuftragBestellenLieferantArtikel> lfArtikel = new List<AuftragBestellenLieferantArtikel>();

                    foreach (DataRow row in data.Rows)
                    {
                        // Wir holen uns erstmal alle Lieferanten
                        var lieferant = ObjectErweiterung.DataRowZuObjekt(new AuftragBestellenLieferant(), row);
                        


                        var sucheLieferant = Lieferanten.Where(x => x.LieferantId == lieferant.LieferantId);
                        if (!sucheLieferant.Any())
                        {
                            Lieferanten.Add(lieferant);
                        }

                        lfArtikel.Add(ObjectErweiterung.DataRowZuObjekt(new AuftragBestellenLieferantArtikel(position.Artikelnummer, sucheLieferant.Any() ? sucheLieferant.First() : lieferant), row));
                    }

                    var artikel = new AuftragBestellenArtikel(position.Artikelnummer, position.Menge);
                    ArtikelZuLieferantenArtikel.Add(artikel, lfArtikel.ToArray());
                    var günstigsterLfArtikel = GetGünstigstenLieferantenArtikel(artikel);
                    if (günstigsterLfArtikel is null)
                    {
                        var sucheNichtVerfügbarArtikel = ArtikelNichtVerfügbar.Where(x => x.Artikelnummer.Equals(position.Artikelnummer));
                        if (sucheNichtVerfügbarArtikel.Any())
                        {
                            foreach (var nichtVerfügbarArtikel in sucheNichtVerfügbarArtikel)
                            {
                                nichtVerfügbarArtikel.Menge += artikel.Menge;
                            }
                        }
                        else
                        {
                            ArtikelNichtVerfügbar.Add(artikel);
                        }
                    }

                    Artikel.Add(artikel);

                }
            }
        }


        /// <summary>
        /// Fügt eine neue <see cref="AuftragBestellenBestellung"/> zum Controller hinzu.
        /// </summary>
        /// <returns>
        /// Ein <see cref="AuftragBestellenBestellung"/>-Objekt wenn eine Bestellung hinzugefügt wurde, ansonsten null.
        /// <para>Bestellungen können nur dann hinzugefügt werden, wenn die Eigenschaft <see cref="CanAddBestellung"/> true zurückgibt.</para>
        /// </returns>
        public AuftragBestellenBestellung? AddBestellung()
        {
            if (CanAddBestellung)
            {
                var neueBestellung = new AuftragBestellenBestellung(this);
                Bestellungen.Add(neueBestellung);

                return neueBestellung;
            }

            return null;
        }
        /// <summary>
        /// Entfernt die angebene <see cref="AuftragBestellenBestellung"/> vom Controller.
        /// </summary>
        /// <param name="bestellung"></param>
        public void RemoveBestellung(AuftragBestellenBestellung bestellung)
        {
            Bestellungen.Remove(bestellung);
        }
    }
}
