#if DEBUG
#define KEEP_BUCHUNG // Wenn aktiviert, dann löscht die WK5 im Debugmodus keine Buchungen aus WK5_BUCHUNG und WK5_BUCHUNG_SN. Das erleichtert das testen des Lagerregals enorm
#endif
using KarleyLibrary.Erweiterungen;
using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using System.Threading.Tasks;
using System.Linq;

namespace WK5.Core.Models.Tools.Lagerregal
{
    public class PackBelegposition : Belegposition
    {

        #region Eigenschaften


        public string ARTI_A_EAN { get; set; } = String.Empty;
        public string ARTI_A_HERST_NR { get; set; } = String.Empty;
        public string ARTI_B_PACKTEXT { get; set; } = string.Empty;

        public decimal BEREITS_GEBUCHT { get; set; }

        public bool Ausgeliefert { get; private set; }

        /// <summary>
        /// Ruft den Namen des Kunden ab, dem die Seriennummer zugeordnet werden soll.
        /// </summary>
        public string Kundenname { get; set; } = String.Empty;
        public bool ShowWarenzugangHeuteHinweis { get; internal set; }

        /// <summary>
        /// Ruft einen Wert ab, der angibt, ob alle Positionen vollständig gebucht wurden.
        /// </summary>
        public bool IstGepackt
        {
            get
            {
                bool returnValue = true;

                if (PositionIstStückliste)
                {
                    foreach (PackBelegposition artikel in StücklistenArtikel)
                    {

                        bool tmp = artikel.PositionIstStückliste ? artikel.IstGepackt : artikel.Menge == artikel.BEREITS_GEBUCHT;

                        returnValue = returnValue && tmp;

                    }
                    return returnValue;
                }
                else
                {
                    return Menge == BEREITS_GEBUCHT;
                }

            }
        }

        public string UrlParameter
        {
            get
            {
                PackBelegposition? currentParent = this;
                StringBuilder sb = new StringBuilder();

                List<int> stkPositionsIds = new List<int>();

                while (currentParent != null)
                {
                    // Wenn die PositionsId 0 ist, dann haben wir entweder einen Artikel ohne Stückliste vorliegen, oder die erste Stückliste
                    if (currentParent.StücklistenPositionsId != 0)
                    {
                        stkPositionsIds.Add(currentParent.StücklistenPositionsId);
                    }

                    currentParent = (PackBelegposition)currentParent.Parent!;
                }

                // Die Liste wird umgedreht, damit die Reihenfolge der PositionsIDs aus Sicht der URL passen
                stkPositionsIds.Reverse();

                foreach (int stkPosId in stkPositionsIds)
                {
                    if (sb.Length != 0)
                    {
                        sb.Append('_');
                    }

                    sb.Append(stkPosId.ToString());
                }


                return sb.ToString();
            }
        }
        public string Url
        {
            get
            {
                StringBuilder sb = new StringBuilder();

                sb.Append("/Lagerregal/Auftrag/");
                sb.Append(BPOS_N_NR);

                if (PositionIstStückliste)
                {
                    sb.Append('/');
                    sb.Append("Stueckliste");
                }

                sb.Append('/');
                sb.Append(PosId);

                if (!String.IsNullOrWhiteSpace(this.UrlParameter))
                {
                    sb.Append('/');
                    sb.Append(UrlParameter);
                }


                return sb.ToString();
            }
        }
        #endregion

        internal PackBelegposition()
        {

        }


        #region Position Laden
        protected override async Task<Belegposition?> LadePositionAsync(FbController2 fbController)
        {
            if (PosId == 0)
            {
                return null;
            }

            fbController.AddParameter("@ARTIKELNUMMER", Artikelnummer);
            var row = await fbController.SelectRowAsync("SELECT ARTI_A_EAN, ARTI_A_HERST_NR, ARTI_A_UNTERWARENG, ARTI_B_PACKTEXT FROM ARTIKEL WHERE ARTI_A_NR = @ARTIKELNUMMER");

            // Row kann null sein, bei Artikeln, die in der W4 nicht existieren. Z.B. T1 oder TEXT
            if (row is not null)
            {
                ObjectErweiterung.DataRowZuObjekt(this, row);
            }

            Menge = Parent != null ? Menge * Parent.Menge : Menge;
            Ausgeliefert = Menge == BPOS_N_MENGEGELIEF * BPOS_N_MENGENFAKTOR && ARTI_L_LAGERFUEHR;
            ShowWarenzugangHeuteHinweis = await PrüfeAufLagerzugangAsync(this, fbController);


            if (Menge == 0)
            {
                BEREITS_GEBUCHT = Menge;
            }
            else if (PositionIstStückliste)
            {
                BEREITS_GEBUCHT = 0;
            }
            else
            {
                BEREITS_GEBUCHT = await GetBereitsGebuchtAsync(fbController);
            }

            if (PositionIstStückliste)
            {
                await LadeStücklisteAsync(this, 0, fbController);
            }

            if (PositionBenötigtSeriennummer)
            {
                await LadeSeriennummernAsync(this, fbController);
            }

            return this;
        }

        internal override async Task LadeStücklisteAsync(Belegposition pos, int ebene, FbController2 fbController)
        {
            if (pos == null)
            {
                throw new ArgumentNullException(nameof(pos));
            }

            if (pos.PositionIstStückliste && pos is PackBelegposition packPos)
            {
                // Alle es müssen die Daten für alle Artikel geladen werden, die in der Stückliste sind
                fbController.AddParameter("@BSTU_N_BELEPOSID", pos.PosId);
                fbController.AddParameter("@BSTU_N_EBENE", ebene);
                fbController.AddParameter("@BSTU_N_POSID_UEBER", pos.StücklistenPositionsId);

                // Die Spalten werden extra als AS gekennzeichnet, damit eine automatische Zuordnung über ObjectErweiterung möglich ist.
                string sql = @"SELECT 
BSTU_A_UNTERARTI AS BPOS_A_ARTIKELNR, 
BSTU_N_MENGE AS BPOS_N_MENGE, 
BSTU_N_POS AS ARST_N_POSITION, 
BSTU_N_POSID,
ARTI_L_SN, 
ARTI_A_LAGER, 
ARTI_A_BEZ1 AS BPOS_A_BEZ1,
ARTI_A_BEZ2 AS BPOS_A_BEZ2,
ARTI_A_BEZ3 AS BPOS_A_BEZ3,
ARTI_A_BEZ4 AS BPOS_A_BEZ4,
ARTI_A_BEZ5 AS BPOS_A_BEZ5,
ARTI_L_DIENSTLEIST,
ARTI_B_NOTIZ,
ARTI_N_GEWICHT, ARTI_L_LAGERFUEHR,
ARTI_N_EK,
ARTI_A_EAN, ARTI_A_HERST_NR,
ARTI_L_DIENSTLEIST,
ARTI_A_UNTERWARENG,
ARTI_N_COLLI,
ARTI_B_PACKTEXT,
(SELECT CASE WHEN COUNT(*) > 0 THEN 'Y' ELSE 'N' END FROM BELEGSTUECKLISTE WHERE BSTU_N_POSID_UEBER = BST.BSTU_N_POSID) AS IST_L_STUECKLISTE,
(SELECT ARWE_N_BESTAND FROM ARTIKELWERTE WHERE UPPER(ARWE_A_NR) = BST.BSTU_A_UNTERARTI) AS Bestand
FROM BELEGSTUECKLISTE BST 
INNER JOIN ARTIKEL A ON (A.ARTI_A_NR = BST.BSTU_A_UNTERARTI)
WHERE BSTU_N_BELEPOSID = @BSTU_N_BELEPOSID AND BSTU_N_EBENE = @BSTU_N_EBENE";
                if (ebene > 0)
                {
                    sql += " AND BSTU_N_POSID_UEBER = @BSTU_N_POSID_UEBER";
                }
                var stücklistenArtikel = await fbController.SelectDataAsync(sql);

                foreach (DataRow artikel in stücklistenArtikel.Rows)
                {
                    PackBelegposition stücklistenPosition = (PackBelegposition)packPos.Clone();
                    stücklistenPosition.Parent = packPos; // Als ersten Schritt, da andere Funktionen auf den Parent aufbauen
                    ObjectErweiterung.DataRowZuObjekt(stücklistenPosition, artikel);
                    stücklistenPosition.BEREITS_GEBUCHT = await stücklistenPosition.GetBereitsGebuchtAsync(fbController); // Erfordert eine gültige StücklistenPositionsId
                    stücklistenPosition.Ausgeliefert = stücklistenPosition.Menge == stücklistenPosition.BPOS_N_MENGEGELIEF * stücklistenPosition.BPOS_N_MENGENFAKTOR && stücklistenPosition.ARTI_L_LAGERFUEHR;
                    stücklistenPosition.ShowWarenzugangHeuteHinweis = await PrüfeAufLagerzugangAsync(stücklistenPosition, fbController);

                    if (stücklistenPosition.PositionBenötigtSeriennummer)
                    {
                        await LadeSeriennummernAsync(stücklistenPosition, fbController);
                    }

                    packPos.StücklistenArtikel.Add(stücklistenPosition);

                    if (stücklistenPosition.PositionIstStückliste)
                    {
                        await LadeStücklisteAsync(stücklistenPosition, ebene + 1, fbController);
                    }
                }
            }
        }

        internal override async Task LadeSeriennummernAsync(Belegposition pos, FbController2 fbController)
        {
            if (pos == null)
            {
                throw new ArgumentNullException(nameof(pos));
            }

            if (!pos.PositionBenötigtSeriennummer)
            {
                return;
            }

            fbController.AddParameter("@BELEGNUMMER", pos.BPOS_N_NR);
            fbController.AddParameter("@BUCHUNGSN_N_BPOSNR", pos.PosId);
            fbController.AddParameter("@BUCHUNGSN_N_STUECKLISTEPOSID", pos.StücklistenPositionsId);

            // Bereits gebucht Seriennummern laden
            var seriennummern = await fbController.SelectDataAsync("SELECT BUCHUNGSN_A_SN FROM WK5_BUCHUNG_SN WHERE BUCHUNGSN_N_BELEGNR = @BELEGNUMMER AND BUCHUNGSN_N_BPOSNR = @BUCHUNGSN_N_BPOSNR AND BUCHUNGSN_N_STUECKLISTEPOSID = @BUCHUNGSN_N_STUECKLISTEPOSID");
            foreach (DataRow seriennummer in seriennummern.Rows)
            {
                Seriennummer sn = new Seriennummer()
                {
                    Artikelnummer = pos.Artikelnummer,
                    Nummer = seriennummer["BUCHUNGSN_A_SN"].ToString()!,
                    Menge = 1,
                    Zugang = DateTime.Now,
                    Ausgeliefert = false,
                    Charge = 0                    
                };
                pos.Seriennummern.Add(sn);
            }
        }

        #endregion
        #region Chargen
        private async Task SeriennummerChargenAsync(FbController2 fbController, int personalNummer)
        {
            foreach (Seriennummer seriennummer in Seriennummern)
            {

                fbController.AddParameter("@INP_SN", seriennummer.Nummer);
                fbController.AddParameter("@INP_CHARGE", seriennummer.Charge);
                fbController.AddParameter("@INP_ARTIKEL", Artikelnummer);
                fbController.AddParameter("@INP_LASTUSER", personalNummer);
                fbController.AddParameter("@INP_BELEGPOSID", PosId);
                fbController.AddParameter("@INP_BELEGNAME", Kundenname);
                fbController.AddParameter("@INP_BELEGSTLID", StücklistenPositionsId);

                string? meldung = await fbController.FetchObjectAsync("SELECT OUT_MELDUNG FROM PROZ_SN_SAVE(@INP_SN, @INP_CHARGE, @INP_ARTIKEL, @INP_LASTUSER, @INP_BELEGPOSID, @INP_BELEGNAME, @INP_BELEGSTLID)") as string;

                if (!String.IsNullOrWhiteSpace(meldung))
                {
                    throw new Exception($"Fehler beim Chargen der Seriennummer '{seriennummer}' Fehlermeldung:{meldung}");
                }
            }


        }

        private async Task StücklisteChargenAsync(FbController2 fbController, int personalNummer)
        {
            if (!PositionIstStückliste)
            {
                return;
            }

            foreach (PackBelegposition stücklistenposition in StücklistenArtikel)
            {
                await stücklistenposition.ChargenAsync(fbController, personalNummer);
            }
        }

        private async Task ArtikelChargenAsync(FbController2 fbController)
        {

            fbController.AddParameter("@ARTIKELNUMMER", Artikelnummer);
            var chargenListe = await fbController.SelectDataAsync("SELECT CHAR_N_NR, CHAR_N_MENGEOFFEN FROM ARTIKEL_CHARGEN WHERE CHAR_A_ARTINR = @ARTIKELNUMMER AND CHAR_N_MENGEOFFEN > 0 ORDER BY ZUGA_D_DATUM");

            decimal BEREITS_GECHARGT = 0; // Zählt hoch, wie viele von der BEREITS_GEBUCHTEN Menge gechargt wurde.

            if (BEREITS_GEBUCHT > 0)
            {
                foreach (DataRow charge in chargenListe.Rows)
                {
                    decimal.TryParse(charge["CHAR_N_MENGEOFFEN"].ToString(), out decimal CHAR_N_MENGEOFFEN);
                    int.TryParse(charge["CHAR_N_NR"].ToString(), out int CHAR_N_NR);

                    decimal zuChargen = BEREITS_GEBUCHT - BEREITS_GECHARGT;

                    if (CHAR_N_MENGEOFFEN < zuChargen)
                    {
                        zuChargen = CHAR_N_MENGEOFFEN;
                    }

                    fbController.AddParameter("@INP_POSID", PosId);
                    fbController.AddParameter("@INP_STLPOSID", StücklistenPositionsId);
                    fbController.AddParameter("@INP_CHARGE", CHAR_N_NR);
                    fbController.AddParameter("@INP_MENGE", zuChargen);
                    await fbController.RunProcedureAsync("PROZ_BELEGCHARGEN_SAVE");

                    BEREITS_GECHARGT += zuChargen;

                    if (zuChargen <= 0 || BEREITS_GEBUCHT == BEREITS_GECHARGT || (BEREITS_GECHARGT + BPOS_N_MENGEGELIEF == Menge))
                    {
                        break;
                    }
                }


                if (BEREITS_GEBUCHT != BEREITS_GECHARGT)
                {
                    throw new ChargenException(Artikelnummer, BEREITS_GECHARGT);
                }
            }
            chargenListe.Dispose();
        }
        public async Task ChargenAsync(FbController2 fbController, int personalNummer)
        {
            if (ARTI_L_LAGERFUEHR || PositionIstStückliste)
            {
                if (PositionBenötigtSeriennummer && !PositionIstStückliste)
                {
                    await SeriennummerChargenAsync(fbController, personalNummer);
                }
                else if (PositionIstStückliste)
                {
                    await StücklisteChargenAsync(fbController, personalNummer);
                }
                else
                {
                    await ArtikelChargenAsync(fbController);
                }

                if (!PositionIstStückliste)
                {
                    await InsertBelegHistorieAsync(fbController, personalNummer);
                }

#if !KEEP_BUCHUNG
                await AusWK5AusbuchenAsync(fbController);
#endif


            }
        }
        private async Task InsertBelegHistorieAsync(FbController2 fbController, int personalNummer)
        {
            fbController.AddParameter("@BCNG_N_BELEGNR", BPOS_N_NR);
            fbController.AddParameter("@BCNG_A_BELEGTYP", BPOS_A_TYP);
            fbController.AddParameter("@BCNG_A_WERTALT", Artikelnummer);
            fbController.AddParameter("@BCNG_A_WERTNEU", $"Gechargt: {BEREITS_GEBUCHT}");
            int BCNG_N_ID = Convert.ToInt32(await fbController.FetchObjectAsync(@"INSERT INTO BELEG_CHANGE (BCNG_N_BELEGNR, BCNG_A_BELEGTYP, BCNG_N_ART, BCNG_A_WERTALT, BCNG_A_WERTNEU, BCNG_N_POSNR, BCNG_TIMESTAMP) VALUES (@BCNG_N_BELEGNR, @BCNG_A_BELEGTYP, 3, @BCNG_A_WERTALT, @BCNG_A_WERTNEU, 0, CURRENT_TIMESTAMP) RETURNING BCNG_N_ID"));

            // Der User muss nachträglich eingefügt werden
            fbController.AddParameter("@BCNG_N_USER", personalNummer);
            fbController.AddParameter("@BCNG_N_ID", BCNG_N_ID);
            await fbController.QueryAsync("UPDATE BELEG_CHANGE SET BCNG_N_USER = @BCNG_N_USER WHERE BCNG_N_ID = @BCNG_N_ID");
        }
        private async Task AusWK5AusbuchenAsync(FbController2 fbController)
        {
            fbController.AddParameter("@BUCHUNG_N_BELEGNR", BPOS_N_NR);
            fbController.AddParameter("@BUCHUNG_N_BPOSNR", PosId);
            fbController.AddParameter("@BUCHUNG_N_STUECKLISTEPOSID", StücklistenPositionsId);
            await fbController.QueryAsync("DELETE FROM WK5_BUCHUNG WHERE BUCHUNG_N_BELEGNR = @BUCHUNG_N_BELEGNR AND BUCHUNG_N_BPOSNR = @BUCHUNG_N_BPOSNR AND BUCHUNG_N_STUECKLISTEPOSID = @BUCHUNG_N_STUECKLISTEPOSID");

            fbController.AddParameter("@BUCHUNGSN_N_BELEGNR", BPOS_N_NR);
            fbController.AddParameter("@BUCHUNGSN_N_BPOSNR", PosId);
            fbController.AddParameter("@BUCHUNGSN_N_STUECKLISTEPOSID", StücklistenPositionsId);
            await fbController.QueryAsync("DELETE FROM WK5_BUCHUNG_SN WHERE BUCHUNGSN_N_BELEGNR = @BUCHUNGSN_N_BELEGNR AND BUCHUNGSN_N_BPOSNR = @BUCHUNGSN_N_BPOSNR AND BUCHUNGSN_N_STUECKLISTEPOSID = BUCHUNGSN_N_STUECKLISTEPOSID");
        }
        #endregion
        #region Methoden
        internal async Task<decimal> GetBereitsGebuchtAsync(FbController2 fbController)
        {
            fbController.AddParameter("@BUCHUNG_N_BPOSNR", PosId);
            fbController.AddParameter("@BUCHUNG_N_BELEGNR", BPOS_N_NR);
            fbController.AddParameter("@BUCHUNG_A_BELEGTYP", BPOS_A_TYP);
            fbController.AddParameter("@BUCHUNG_N_STUECKLISTEPOSID", StücklistenPositionsId);
            var row = await fbController.SelectRowAsync("SELECT SUM(BUCHUNG_N_MENGE) AS GEBUCHT FROM WK5_BUCHUNG WHERE BUCHUNG_N_BPOSNR = @BUCHUNG_N_BPOSNR AND BUCHUNG_N_BELEGNR = @BUCHUNG_N_BELEGNR AND BUCHUNG_A_BELEGTYP = @BUCHUNG_A_BELEGTYP AND BUCHUNG_N_STUECKLISTEPOSID = @BUCHUNG_N_STUECKLISTEPOSID");

            if (row is null)
            {
                return 0;
            }

            _ = decimal.TryParse(row["GEBUCHT"].ToString(), out decimal GEBUCHT);
            return GEBUCHT;
        }
        internal async Task<bool> PrüfeAufLagerzugangAsync(Belegposition pos, FbController2 fbController)
        {
            fbController.AddParameter("@ARTIKELNUMMER", pos.Artikelnummer);
            var row = await fbController.SelectRowAsync("SELECT FIRST 1 ARBE_TIMESTAMP FROM ARTIKELBEWEGUNG WHERE ARBE_A_ART = 'ZUGA' AND ARBE_A_ARTINR = @ARTIKELNUMMER ORDER BY ARBE_TIMESTAMP DESC");

            if (row == null)
            {
                return false;
            }

            return ((DateTime)row["ARBE_TIMESTAMP"]).Date == DateTime.Now.Date;
        }
        public bool HatStückListeBuchungen()
        {
            bool returnValue = true;

            if (!PositionIstStückliste)
            {
                return false;
            }

            foreach (PackBelegposition artikel in StücklistenArtikel)
            {
                bool tmp = artikel.PositionIstStückliste ? artikel.HatStückListeBuchungen() : !(artikel.BEREITS_GEBUCHT > 0);
                returnValue = returnValue && tmp;
            }

            return returnValue;
        }

        public bool HatBundleBuchungen()
        {


            if (!IstBundle)
            {
                return false;
            }

            foreach (PackBelegposition artikel in BundleArtikel)
            {
                if (artikel.BEREITS_GEBUCHT > 0)
                {
                    return true;
                }
            }

            return false;
        }

        public int GetBuchungsmengeBundle()
        {
            if (!IstBundle)
            {
                return 0;
            }

            if (!HatBundleBuchungen())
            {
                return 0;
            }

            Dictionary<string, decimal> bundleMengen = new();
            Dictionary<string, decimal> gebuchtMengen = new();
            foreach (PackBelegposition artikel in BundleArtikel)
            {
                // Als erstes müssen die Basis Mengen des Bundles ermittelt werden
                decimal basisMenge = artikel.Menge / Menge;

                if (bundleMengen.ContainsKey(artikel.Artikelnummer))
                {
                    bundleMengen[artikel.Artikelnummer] += basisMenge;
                }
                else
                {
                    bundleMengen.Add(artikel.Artikelnummer, basisMenge);
                }

                // Als nächstes muss ermittelt werden, wie oft die Bundlepositionen bereits gebucht worden sind.
                decimal gebuchtMenge = artikel.BEREITS_GEBUCHT;
                if (gebuchtMengen.ContainsKey(artikel.Artikelnummer))
                {
                    gebuchtMengen[artikel.Artikelnummer] += gebuchtMenge;
                }
                else
                {
                    gebuchtMengen.Add(artikel.Artikelnummer, gebuchtMenge);
                }
            }

            // Wir gehen im besten Fall davon aus, dass das Maximum bereits gebucht wurde
            decimal totalGebucht = Menge;


            // Nun prüfen wir, wie oft das Bundle vollständig gebucht wurde
            foreach (var item in bundleMengen)
            {
                decimal gebucht = gebuchtMengen[item.Key];


                if (gebucht == 0)
                {
                    return 0;
                }

                // Wir können nur vollständige Bundles ausliefern
                int tmp = (int)Math.Floor(gebucht / item.Value);
                if (tmp < totalGebucht)
                {
                    totalGebucht = tmp;
                }
            }

            return (int)totalGebucht;
        }
        #endregion
        #region Schnittstelle ICloneable
        /// <summary>
        /// Erstellt eine Tiefe Kopie der Belegposition
        /// </summary>
        /// <returns></returns>
        public sealed override object Clone()
        {
            PackBelegposition copy = (PackBelegposition)this.MemberwiseClone();
            copy.StücklistenArtikel = new List<Belegposition>();
            copy.Seriennummern = new List<Seriennummer>();
            return copy;
        }
        #endregion
    }
}
