#if DEBUG
#define GODMODE // Mit GODMODE kann im DEBUG Modus GODMODE eingegeben werden. GODMODE = JEDE ARTIKELNUMMER
#endif
using KarleyLibrary.Erweiterungen;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using WK5.Core.Models;
using WK5.Core.Models.Tools.Lagerregal;


namespace WK5.Core.Services
{
    /// <summary>
    /// Dieser Service stellt Funktionen zum verbuchen von Belegpositionen in der WK5 zur Verfügung.
    /// </summary>
    public class BuchenService
    {
        /// <summary>
        /// Bucht eine Position in der WK5 aus.
        /// </summary>
        /// <param name="buchung"></param>
        /// <returns></returns>
        private async Task<BuchungResponse> BuchenAsync(Buchungsanfrage buchung, FbController2 fbController)
        {
            PackAuftrag? beleg = await PackAuftrag.GetPackAuftragAsync(buchung.BUCHUNG_N_BELEGNR, fbController);
            #region Prüfung
            if (beleg == null)
            {
                return new BuchungResponse()
                {
                    Status = BuchungStatus.AuftragNichtGefunden,
                    Success = false,
                    Message = "Buchung nicht erfolgreich. Der Auftrag konnte nicht gefunden werden."
                };
            }

            if (!beleg.ZumPackenFreigegeben)
            {
                return new BuchungResponse()
                {
                    Status = BuchungStatus.AuftragNichtFreigegeben,
                    Success = false,
                    Message = "Buchung nicht erfolgreich. Der Auftrag ist nicht zum Packen freigegeben."
                };
            }

            PackBelegposition? position = beleg.GetPosition(buchung.BUCHUNG_N_BPOSNR);

            if (position == null)
            {
                return new BuchungResponse()
                {
                    Status = BuchungStatus.PositionsIdNichtGefunden,
                    Success = false,
                    Message = $"Buchung nicht erfolgreich. Die Position {buchung.BUCHUNG_N_BPOSNR} konnte nicht gefunden werden."
                };
            }

            if (buchung.BUCHUNG_N_STUECKLISTEPOSID != 0)
            {
                position = beleg.GetPosition(position, buchung.BUCHUNG_N_STUECKLISTEPOSID);
            }

            if (position == null)
            {
                return new BuchungResponse()
                {
                    Status = BuchungStatus.StücklistenPositionsIdNichtGefunden,
                    Success = false,
                    Message = $"Buchung nicht erfolgreich. Die Position der Stückliste {buchung.BUCHUNG_N_BPOSNR}_{buchung.BUCHUNG_N_STUECKLISTEPOSID} konnte nicht gefunden werden."
                };
            }

            if (position.PositionIstStückliste)
            {
                return new BuchungResponse()
                {
                    Status = BuchungStatus.BuchungVonStücklisteNichtMöglich,
                    Success = false,
                    Message = $"Buchung nicht erfolgreich. Die Position ist eine Stückliste. Bitte buchen Sie stattdessen die Positionen der Stückliste."
                };
            }

            if (position.PositionBenötigtSeriennummer && !position.IstBundle)
            {
                // Seriennummern können nur einzeln ausgebucht werden, da alles andere sinnlos wäre.
                buchung.BUCHUNG_N_MENGE = 1;
                if (String.IsNullOrWhiteSpace(buchung.SERIENNUMMER))
                {
                    return new BuchungResponse()
                    {
                        Status = BuchungStatus.SeriennummerBenötigt,
                        Success = false,
                        Message = $"Zum buchen der Position wird eine Seriennummer benötigt."
                    };
                }

                fbController.AddParameter("@SERIENNUMMER", buchung.SERIENNUMMER);
                var checkSeriennummer = await fbController.SelectRowAsync("SELECT * FROM SN WHERE SNNR_A_SN = @SERIENNUMMER");

                if (checkSeriennummer == null)
                {
                    return new BuchungResponse()
                    {
                        Status = BuchungStatus.SeriennummerNichtGefunden,
                        Success = false,
                        Message = $"Buchung fehlgeschlagen. Die Seriennummer \"{buchung.SERIENNUMMER}\" konnte nicht gefunden werden."
                    };
                }

                fbController.AddParameter("@SERIENNUMMER", buchung.SERIENNUMMER);
                fbController.AddParameter("@ARTIKELNUMMER", position.Artikelnummer);
                checkSeriennummer = await fbController.SelectRowAsync("SELECT * FROM VIEW_SN WHERE SNNR_A_SN = @SERIENNUMMER AND SNNR_L_AUSGELIEFERT = 'N' AND CHAR_A_ARTINR = @ARTIKELNUMMER");
                if (checkSeriennummer == null)
                {
                    return new BuchungResponse()
                    {
                        Status = BuchungStatus.SeriennummerBereitsVerwendet,
                        Success = false,
                        Message = $"Buchung fehlgeschlagen. Die Seriennummer \"{buchung.SERIENNUMMER}\" wurde bereits durch einen anderen Mitarbeiter verbucht. Bitte wählen Sie eine andere Seriennummer aus."
                    };
                }

                fbController.AddParameter("@SERIENNUMMER", buchung.SERIENNUMMER);
                fbController.AddParameter("@ARTIKELNUMMER", position.Artikelnummer);
                checkSeriennummer = await fbController.SelectRowAsync(@"SELECT * FROM WK5_BUCHUNG_SN WBS 
INNER JOIN BELEGPOS BP ON BP.BPOS_N_POSID = WBS.BUCHUNGSN_N_BPOSNR
LEFT JOIN BELEGSTUECKLISTE BS ON (BS.BSTU_N_BELEPOSID = WBS.BUCHUNGSN_N_BPOSNR AND BS.BSTU_N_POSID = WBS.BUCHUNGSN_N_STUECKLISTEPOSID)
WHERE BUCHUNGSN_A_SN = @SERIENNUMMER AND BP.BPOS_A_ARTIKELNR = @ARTIKELNUMMER");
                if (checkSeriennummer != null)
                {
                    return new BuchungResponse()
                    {
                        Status = BuchungStatus.SeriennummerBereitsVerwendet,
                        Success = false,
                        Message = $"Buchung fehlgeschlagen. Die Seriennummer \"{buchung.SERIENNUMMER}\" wurde bereits durch einen anderen Mitarbeiter verbucht. Bitte wählen Sie eine andere Seriennummer aus."
                    };
                }
            }

            #endregion
            #region BUCHUNG_N_MENGE Korrigieren
            // Hier wird die Buchungsmenge angepasst. Wenn versucht wird mehr zu Buchen, als die Position erfordert, dann wird die Buchung auf das Maximum reduziert.
            decimal zuBuchen = buchung.BUCHUNG_N_MENGE + position.BEREITS_GEBUCHT;
            if (zuBuchen > position.Menge)
            {
                zuBuchen = position.Menge;
            }


            #endregion
            #region Eintragen der Buchung in WK5_BUCHUNG
            fbController.AddParameter("@BPOS_N_POS", buchung.BUCHUNG_N_BPOSNR);
            fbController.AddParameter("@BPOS_N_NR", buchung.BUCHUNG_N_BELEGNR);
            fbController.AddParameter("@MENGE", zuBuchen);
            fbController.AddParameter("@BUCHUNG_A_BELEGTYP", "AU");
            fbController.AddParameter("@BUCHUNG_N_STUECKLISTEPOSID", buchung.BUCHUNG_N_STUECKLISTEPOSID);

            try
            {
                await fbController.QueryAsync("UPDATE OR INSERT INTO WK5_BUCHUNG (BUCHUNG_A_BELEGTYP, BUCHUNG_N_BELEGNR, BUCHUNG_N_BPOSNR, BUCHUNG_N_STUECKLISTEPOSID,  BUCHUNG_N_MENGE) VALUES (@BUCHUNG_A_BELEGTYP, @BPOS_N_NR, @BPOS_N_POS, @BUCHUNG_N_STUECKLISTEPOSID, @MENGE)");
            }
            catch (Exception ex)
            {
                return new BuchungResponse()
                {
                    Status = BuchungStatus.SqlFehler,
                    Success = false,
                    Message = ex.Message,
                    Value = ex
                };
            }
            #endregion
            #region Seriennummern Buchen
            if (position.PositionBenötigtSeriennummer && buchung.SERIENNUMMER != null)
            {
                fbController.AddParameter("@BUCHUNGSN_N_BELEGNR", buchung.BUCHUNG_N_BELEGNR);
                fbController.AddParameter("@BUCHUNGSN_N_BPOSNR", buchung.BUCHUNG_N_BPOSNR);
                fbController.AddParameter("@BUCHUNGSN_A_SN", buchung.SERIENNUMMER);
                fbController.AddParameter("@BUCHUNGSN_A_BELEGTYP", "AU");
                fbController.AddParameter("@BUCHUNGSN_N_STUECKLISTEPOSID", buchung.BUCHUNG_N_STUECKLISTEPOSID);

                try
                {
                    await fbController.QueryAsync("UPDATE OR INSERT INTO WK5_BUCHUNG_SN (BUCHUNGSN_A_BELEGTYP, BUCHUNGSN_N_BELEGNR, BUCHUNGSN_N_BPOSNR, BUCHUNGSN_N_STUECKLISTEPOSID, BUCHUNGSN_A_SN) VALUES (@BUCHUNGSN_A_BELEGTYP, @BUCHUNGSN_N_BELEGNR, @BUCHUNGSN_N_BPOSNR, @BUCHUNGSN_N_STUECKLISTEPOSID, @BUCHUNGSN_A_SN)");
                }
                catch (Exception ex)
                {
                    return new BuchungResponse()
                    {
                        Status = BuchungStatus.SqlFehler,
                        Message = ex.Message,
                        Success = false,
                        Value = fbController.CommandText
                    };
                }
            }
            #endregion

            return new BuchungResponse()
            {
                Status = BuchungStatus.OK,
                Message = "Buchung erfolgreich durchgeführt",
                Success = true,
                Gebucht = zuBuchen,
                Seriennummer = buchung.SERIENNUMMER
            };
        }
        /// <summary>
        /// Prüft Eingaben für eine Position und bucht diese bei Erfolg in der WK5 aus.
        /// </summary>
        /// <param name="position"></param>
        /// <param name="buchungsMenge"></param>
        /// <param name="eingabeArtikelnummer"></param>
        /// <param name="eingabeSeriennummer"></param>
        /// <returns></returns>
        public async Task<BuchungResponse> BuchenAsync(PackBelegposition position, decimal buchungsMenge, string eingabeArtikelnummer, string? ausgewählteSeriennummer, FbController2 fbController)
        {
            var (gültig, seriennummer) = await PrüfeArtikelnummer(position, eingabeArtikelnummer, fbController);

            if (!gültig)
            {
                return new BuchungResponse()
                {
                    Status = BuchungStatus.UngültigeArtikelnummer,
                    Success = false,
                    Message = "Falsche Artikelnummer"
                };
            }
            // Falls wir keine Seriennummer ausgewählt haben, aber eine eingegeben haben, dann soll diese automatisch ausgewählt werden
            if (String.IsNullOrWhiteSpace(ausgewählteSeriennummer))
            {
                ausgewählteSeriennummer = seriennummer;
            }

            BuchungResponse response = await BuchenAsync(new Buchungsanfrage()
            {
                BUCHUNG_N_BELEGNR = position.BPOS_N_NR,
                BUCHUNG_N_BPOSNR = position.PosId,
                BUCHUNG_N_MENGE = buchungsMenge,
                BUCHUNG_N_STUECKLISTEPOSID = position.StücklistenPositionsId,
                SERIENNUMMER = ausgewählteSeriennummer
            }, fbController);


            if (response.Success)
            {
                // Wir setzen die Gebucht Menge nur einmal, anstatt an allen Stellen
                position.BEREITS_GEBUCHT = response.Gebucht;

                // Prüfen, ob die Buchung des Bundles beeinflusst wurde
                if (position.BundleParent is not null)
                {
                    var bundlePos = (PackBelegposition)position.BundleParent;
                    int totalBundleGebucht = bundlePos.GetBuchungsmengeBundle();
                    await SetBundleBuchungAsync(bundlePos, totalBundleGebucht, fbController);
                    bundlePos.BEREITS_GEBUCHT = totalBundleGebucht;
                }
            }

            return response;
        }
        /// <summary>
        /// Prüft die eingabe Artikelnummer, ob diese gültig ist.
        /// <para>
        /// Wird eine Seriennummer eingegeben, wird automatisch der passende Aritkel zurückgegeben.
        /// </para>
        /// </summary>
        /// <param name="position"></param>
        /// <param name="eingabeArtikelnummer"></param>
        /// <param name="fbController"></param>
        /// <returns></returns>
        public async Task<(bool gültig, string? seriennummer)> PrüfeArtikelnummer(PackBelegposition position, string eingabeArtikelnummer, FbController2 fbController)
        {
            eingabeArtikelnummer = eingabeArtikelnummer.ToUpper();

            string alternativArtikel = "";
            try
            {
                Dictionary<string, string> replaceArtikelnummern = await Artikel.GetReplaceArtikelnummernAsync(fbController).ToDictionaryAsync(x => x.Key, x => x.Value);
                alternativArtikel = replaceArtikelnummern[eingabeArtikelnummer].ToUpper();
            }
            catch (Exception)
            {
                // Es gibt für die Eingabe keine alternative Artikelnummer
            }

            string trimArtikelnummer = StringErweiterung.TrimArtikelNummer(position.Artikelnummer);
            List<string> seriennummern = await position.GetFreieSeriennummernAsync(fbController).ToListAsync();
            // Prüfen der Artikelnummer
            bool godmode = false;
#if GODMODE
            godmode = true;
#endif 

            if (position.Artikelnummer.ToUpper() == eingabeArtikelnummer ||
                position.Artikelnummer.ToUpper() + "his" == eingabeArtikelnummer ||
                (godmode && eingabeArtikelnummer.Equals("godmode", StringComparison.OrdinalIgnoreCase)) ||
                (trimArtikelnummer != null && trimArtikelnummer != "" && eingabeArtikelnummer == trimArtikelnummer.ToUpper()) ||
                (position.Artikelnummer == alternativArtikel && alternativArtikel != "") ||
                (position?.ARTI_A_EAN == StringErweiterung.ParseEan(eingabeArtikelnummer)) ||
                (position?.ARTI_A_HERST_NR == eingabeArtikelnummer) ||
                seriennummern.Any(s => s.Equals(eingabeArtikelnummer, StringComparison.OrdinalIgnoreCase)))
            {
                return (true, seriennummern.Where(s => s.Equals(eingabeArtikelnummer, StringComparison.OrdinalIgnoreCase)).FirstOrDefault());
            }
            else
            {
                return (false, null);
            }
        }

        /// <summary>
        /// Entfernt alle Einträge in WK5_BUCHUNG und WK5_BUCHUNG_SN für einen bestimmten Beleg.
        /// </summary>
        /// <param name="belegtyp"></param>
        /// <param name="belegnummer"></param>
        /// <param name="fbController"></param>
        /// <returns></returns>
        public async Task BuchungenLöschenAsync(string belegtyp, int belegnummer, FbController2 fbController)
        {
            fbController.AddParameter("@BUCHUNG_A_BELEGTYP", belegtyp);
            fbController.AddParameter("@BUCHUNG_N_BELEGNR", belegnummer);
            await fbController.QueryAsync("DELETE FROM WK5_BUCHUNG WHERE BUCHUNG_A_BELEGTYP = @BUCHUNG_A_BELEGTYP AND BUCHUNG_N_BELEGNR = @BUCHUNG_N_BELEGNR");
            fbController.AddParameter("@BUCHUNGSN_A_BELEGTYP", belegtyp);
            fbController.AddParameter("@BUCHUNGSN_N_BELEGNR", belegnummer);
            await fbController.QueryAsync("DELETE FROM WK5_BUCHUNG_SN WHERE BUCHUNGSN_A_BELEGTYP = @BUCHUNGSN_A_BELEGTYP AND BUCHUNGSN_N_BELEGNR = @BUCHUNGSN_N_BELEGNR");
        }
        /// <summary>
        /// Setzt die Buchung für die Hauptposition eines Bundles
        /// </summary>
        /// <param name="pos"></param>
        /// <param name="totalGebucht"></param>
        /// <param name="fbController"></param>
        /// <returns></returns>
        private async Task SetBundleBuchungAsync(PackBelegposition pos, int totalGebucht, FbController2 fbController)
        {
            fbController.AddParameter("@BPOS_N_POS", pos.PosId);
            fbController.AddParameter("@BPOS_N_NR", pos.BPOS_N_NR);
            fbController.AddParameter("@MENGE", totalGebucht);
            fbController.AddParameter("@BUCHUNG_A_BELEGTYP", "AU");
            fbController.AddParameter("@BUCHUNG_N_STUECKLISTEPOSID", 0);
            await fbController.QueryAsync("UPDATE OR INSERT INTO WK5_BUCHUNG (BUCHUNG_A_BELEGTYP, BUCHUNG_N_BELEGNR, BUCHUNG_N_BPOSNR, BUCHUNG_N_STUECKLISTEPOSID,  BUCHUNG_N_MENGE) VALUES (@BUCHUNG_A_BELEGTYP, @BPOS_N_NR, @BPOS_N_POS, @BUCHUNG_N_STUECKLISTEPOSID, @MENGE)");
        }
    }
}
