using KarleyLibrary.Erweiterungen;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using WK5.Core.Basis.Filter;
using WK5.Core.Models;
using WK5.Core.Models.Tools.Lagerregal;

namespace WK5.Core.Services
{
    /// <summary>
    /// Stellt Funktionalitäten zum Umgang mit Chargen zur Verfügung.
    /// </summary>
    public class ChargenService
    {
        /// <summary>
        /// Sucht Chargen anhand des <see cref="ChargenFilter"/> in der Datenbank.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="fbController"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async IAsyncEnumerable<Charge> SucheChargenAsync(ChargenFilter filter, FbController2 fbController, [EnumeratorCancellation] CancellationToken cancellationToken)
        {

            DataTable data = await fbController.SelectDataAsync(filter.ToSqlQuery(fbController));

            foreach (DataRow row in data.Rows)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    break;
                }

                yield return ObjectErweiterung.DataRowZuObjekt(new Charge(), row);
            }
        }
        /// <summary>
        /// Sucht Seriennummern anhand des <see cref="SeriennummerFilter"/> in der Datenbank
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="fbController"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async IAsyncEnumerable<Seriennummer> SucheSeriennummernAsync(SeriennummerFilter filter, FbController2 fbController, [EnumeratorCancellation] CancellationToken cancellationToken)
        {
            DataTable data = await fbController.SelectDataAsync(filter.ToSqlQuery(fbController));

            foreach (DataRow row in data.Rows)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    break;
                }

                yield return ObjectErweiterung.DataRowZuObjekt(new Seriennummer(), row);
            }
        }
        
        public async Task CreateAsync(Charge charge, FbController2 fbController)
        {
            int neueChargennummer = Convert.ToInt32(await fbController.FetchObjectAsync("SELECT NEXT VALUE FOR GEN_CHAR_N_NR FROM RDB$DATABASE;"));


            fbController.AddParameter("@CHAR_N_NR", neueChargennummer);
            fbController.AddParameter("@CHAR_N_STAPELNR", charge.CHAR_N_STAPELNR);
            fbController.AddParameter("@CHAR_A_ARTINR", charge.CHAR_A_ARTINR);
            fbController.AddParameter("@CHAR_N_MENGE", charge.CHAR_N_MENGE);
            fbController.AddParameter("@CHAR_N_MENGEOFFEN", charge.CHAR_N_MENGEOFFEN);
            fbController.AddParameter("@CHAR_N_EKPREIS", charge.CHAR_N_EKPREIS);
            fbController.AddParameter("@CHAR_N_EKPREIS_ORG", charge.CHAR_N_EKPREIS_ORG);
            fbController.AddParameter("@CHAR_N_RABATT", 0);
            fbController.AddParameter("@CHAR_N_RABATT2", 0);
            fbController.AddParameter("@CHAR_N_TRANSPORTK", 0);
            fbController.AddParameter("@CHAR_N_STEUERSATZ", charge.CHAR_N_STEUERSATZ);
            fbController.AddParameter("@CHAR_A_NOTIZ", charge.CHAR_A_NOTIZ);
            fbController.AddParameter("@CHAR_N_BESTNR", charge.CHAR_N_BESTNR);
            fbController.AddParameter("@CHAR_N_BESTPOS", charge.CHAR_N_BESTPOS);
            fbController.AddParameter("@CHAR_N_BESTMENGE", charge.CHAR_N_BESTMENGE);
            fbController.AddParameter("@CHAR_N_MENGENFAKTOR", 1);
            fbController.AddParameter("@CHAR_N_LASTUSER", fbController.UserId);
            fbController.AddParameter("@CHAR_N_AB_NR", charge.CHAR_N_AB_NR);
            fbController.AddParameter("@CHAR_L_GEBRAUCHT", charge.CHAR_L_GEBRAUCHT);

            charge.CHAR_N_NR = Convert.ToInt32(await fbController.FetchObjectAsync(@"INSERT INTO CHARGEN
(
CHAR_N_NR, 
CHAR_N_STAPELNR, 
CHAR_A_ARTINR, 
CHAR_N_MENGE, 
CHAR_N_MENGEOFFEN,
CHAR_N_EKPREIS, 
CHAR_N_EKPREIS_ORG, 
CHAR_N_RABATT, 
CHAR_N_RABATT2, 
CHAR_N_TRANSPORTK,
CHAR_A_NOTIZ, 
CHAR_N_BESTNR, 
CHAR_N_BESTPOS, 
CHAR_N_BESTMENGE, 
CHAR_N_MENGENFAKTOR,
CHAR_N_AB_NR,
CHAR_L_GEBRAUCHT,
CHAR_TIMESTAMP, 
CHAR_N_LASTUSER,
CHAR_N_STEUERSATZ
)
VALUES
(
@CHAR_N_NR, 
@CHAR_N_STAPELNR, 
@CHAR_A_ARTINR, 
@CHAR_N_MENGE, 
@CHAR_N_MENGEOFFEN,
@CHAR_N_EKPREIS, 
@CHAR_N_EKPREIS_ORG, 
@CHAR_N_RABATT, 
@CHAR_N_RABATT2, 
@CHAR_N_TRANSPORTK,
@CHAR_A_NOTIZ, 
@CHAR_N_BESTNR, 
@CHAR_N_BESTPOS, 
@CHAR_N_BESTMENGE, 
@CHAR_N_MENGENFAKTOR,
@CHAR_N_AB_NR,
@CHAR_L_GEBRAUCHT,
CURRENT_TIMESTAMP, 
@CHAR_N_LASTUSER,
@CHAR_N_STEUERSATZ
) 
RETURNING CHAR_N_NR"));
        }

        /// <summary>
        /// Updatet eine Charge mit den Werten die in dem Objekt charge angegeben sind
        /// </summary>
        /// <param name="charge">Das Objekt mit den aktualisierten werdten</param>
        /// <param name="fbController">Der FbController2 über den die SQL Query ausgeführt werden soll</param>
        /// <returns></returns>
        public async Task UpdateChargeAsync(Charge charge, FbController2 fbController)
        {
            fbController.AddParameter("@CHAR_A_ARTINR", charge.CHAR_A_ARTINR);
            fbController.AddParameter("@CHAR_A_GRUND", charge.CHAR_A_GRUND);
            fbController.AddParameter("@CHAR_A_HERSTELLER", charge.CHAR_A_HERSTELLER);
            fbController.AddParameter("@CHAR_A_HILFSMITTEL", charge.CHAR_A_HILFSMITTEL);
            fbController.AddParameter("@CHAR_A_LAGERPLATZ", charge.CHAR_A_LAGERPLATZ);
            fbController.AddParameter("@CHAR_A_NOTIZ", charge.CHAR_A_NOTIZ);
            fbController.AddParameter("@CHAR_B_NOTIZEN", charge.CHAR_B_NOTIZEN);
            fbController.AddParameter("@CHAR_D_MHD", charge.CHAR_D_MHD);
            fbController.AddParameter("@CHAR_L_GESPERRT", charge.CHAR_L_GESPERRT);
            fbController.AddParameter("@CHAR_L_STEUERFREI", charge.CHAR_L_STEUERFREI);
            fbController.AddParameter("@CHAR_N_AB_NR", charge.CHAR_N_AB_NR);
            fbController.AddParameter("@CHAR_N_BESTNR", charge.CHAR_N_BESTNR);
            fbController.AddParameter("@CHAR_N_BESTPOS", charge.CHAR_N_BESTPOS);
            fbController.AddParameter("@CHAR_N_FA", charge.CHAR_N_FA);
            fbController.AddParameter("@CHAR_N_KONTROLLNR", charge.CHAR_N_KONTROLLNR);
            fbController.AddParameter("@CHAR_N_LASTUSER", charge.CHAR_N_LASTUSER);
            fbController.AddParameter("@CHAR_N_NR", charge.CHAR_N_NR);
            fbController.AddParameter("@CHAR_N_PROJEKT_NR", charge.CHAR_N_PROJEKT_NR);
            fbController.AddParameter("@CHAR_N_STAPELNR", charge.CHAR_N_STAPELNR);
            fbController.AddParameter("@CHAR_N_STEUERSATZ", charge.CHAR_N_STEUERSATZ);
            fbController.AddParameter("@CHAR_N_EKPREIS", charge.CHAR_N_EKPREIS);
            fbController.AddParameter("@CHAR_N_EKPREIS_ORG", charge.CHAR_N_EKPREIS_ORG);
            fbController.AddParameter("@CHAR_N_MENGE", charge.CHAR_N_MENGE);
            fbController.AddParameter("@CHAR_N_MENGENFAKTOR", charge.CHAR_N_MENGENFAKTOR);
            fbController.AddParameter("@CHAR_N_BESTMENGE", charge.CHAR_N_BESTMENGE);
            fbController.AddParameter("@CHAR_N_MENGEOFFEN", charge.CHAR_N_MENGEOFFEN);
            fbController.AddParameter("@CHAR_N_RABATT", charge.CHAR_N_RABATT);
            fbController.AddParameter("@CHAR_N_RABATT2", charge.CHAR_N_RABATT2);
            fbController.AddParameter("@CHAR_N_TRANSPORTK", charge.CHAR_N_TRANSPORTK);
            fbController.AddParameter("@CHAR_TIMESTAMP", charge.CHAR_TIMESTAMP);

            string sql = @"UPDATE CHARGEN SET
CHAR_A_ARTINR = @CHAR_A_ARTINR,
CHAR_A_GRUND = @CHAR_A_GRUND,
CHAR_A_HERSTELLER = @CHAR_A_HERSTELLER,
CHAR_A_HILFSMITTEL = @CHAR_A_HILFSMITTEL,
CHAR_A_LAGERPLATZ = @CHAR_A_LAGERPLATZ,
CHAR_A_NOTIZ = @CHAR_A_NOTIZ,
CHAR_B_NOTIZEN = @CHAR_B_NOTIZEN,
CHAR_D_MHD = @CHAR_D_MHD,
CHAR_L_GESPERRT = @CHAR_L_GESPERRT,
CHAR_L_STEUERFREI = @CHAR_L_STEUERFREI,
CHAR_N_AB_NR = @CHAR_N_AB_NR,
CHAR_N_BESTNR = @CHAR_N_BESTNR,
CHAR_N_BESTPOS = @CHAR_N_BESTPOS,
CHAR_N_FA = @CHAR_N_FA,
CHAR_N_KONTROLLNR = @CHAR_N_KONTROLLNR,
CHAR_N_LASTUSER = @CHAR_N_LASTUSER,
CHAR_N_PROJEKT_NR = @CHAR_N_PROJEKT_NR,
CHAR_N_STAPELNR = @CHAR_N_STAPELNR,
CHAR_N_STEUERSATZ = @CHAR_N_STEUERSATZ,
CHAR_N_EKPREIS = @CHAR_N_EKPREIS,
CHAR_N_EKPREIS_ORG = @CHAR_N_EKPREIS_ORG,
CHAR_N_MENGE = @CHAR_N_MENGE,
CHAR_N_MENGENFAKTOR = @CHAR_N_MENGENFAKTOR,
CHAR_N_BESTMENGE = @CHAR_N_BESTMENGE,
CHAR_N_MENGEOFFEN = @CHAR_N_MENGEOFFEN,
CHAR_N_RABATT = @CHAR_N_RABATT,
CHAR_N_RABATT2 = @CHAR_N_RABATT2,
CHAR_N_TRANSPORTK = @CHAR_N_TRANSPORTK,
CHAR_TIMESTAMP = @CHAR_TIMESTAMP
WHERE CHAR_N_NR = @CHAR_N_NR";

            await fbController.QueryAsync(sql);

        }
        /// <summary>
        /// Chargt eine Position in deren Menge vollautomatisch
        /// </summary>
        /// <param name="pos"></param>
        /// <param name="fbController"></param>
        /// <exception cref="ChargenException">Wird ausgelöst, wenn für die Menge nicht ausreichend offene Chargen vorhanden sind.</exception>
        /// <returns>Liefert den EK-Preis für die Position in der Menge 1 zurück</returns>
        public async Task<decimal> AutoChargenAsync(Belegposition pos, bool neuesteZuerst, FbController2 fbController)
        {
            ChargenFilter chargenFilter = new ChargenFilter
            {
                NurOffene = true,
                NeuesteZuerst = neuesteZuerst
            };

            if (!pos.PositionIstStückliste && !pos.IstBundle && pos.PositionBenötigtSeriennummer)
            {
                return pos.PosEinkaufspreis;
            }

            if (pos.PositionIstStückliste)
            {
                decimal ek = 0;
                foreach (var subPos in pos.StücklistenArtikel)
                {
                    ek = await AutoChargenAsync(subPos, neuesteZuerst, fbController);
                }

                return ek;
            }
            else if (pos.IstBundle)
            {
                decimal ek = 0;
                foreach (var subPos in pos.BundleArtikel)
                {
                    ek = await AutoChargenAsync(subPos, neuesteZuerst, fbController);
                }

                return ek;
            }
            else
            {
                chargenFilter.Artikelnummer = pos.Artikelnummer;
                List<Charge> offeneChargen = await SucheChargenAsync(chargenFilter, fbController, default).ToListAsync();



                decimal gebucht = 0; // Zählt hoch, wie viele chargen bereits zugewiesen worden sind
                decimal ek = pos.PosEinkaufspreis;

                foreach (Charge charge in offeneChargen)
                {
                    decimal zuChargen = pos.Menge - gebucht;
                    if (charge.CHAR_N_MENGEOFFEN < zuChargen)
                    {
                        zuChargen = charge.CHAR_N_MENGEOFFEN;
                    }



                    fbController.AddParameter("@INP_POSID", pos.PosId);
                    fbController.AddParameter("@INP_STLPOSID", pos.StücklistenPositionsId);
                    fbController.AddParameter("@INP_CHARGE", charge.CHAR_N_NR);
                    fbController.AddParameter("@INP_MENGE", zuChargen);
                    ek = Convert.ToDecimal(await fbController.FetchObjectAsync("execute procedure PROZ_BELEGCHARGEN_SAVE(@INP_POSID, @INP_STLPOSID, @INP_CHARGE, @INP_MENGE)"));

                    gebucht += zuChargen;

                    if (zuChargen <= 0 || pos.Menge == gebucht)
                    {
                        break;
                    }
                }


                if (pos.Menge != gebucht)
                {
                    throw new ChargenException(pos.Artikelnummer, gebucht);
                }
                else
                {
                    pos.Gechargt = gebucht;
                }


                return ek;
            }

            return pos.PosEinkaufspreis;
        }
        /// <summary>
        /// Entfernt alle Chargen zu einer Belegposition.
        /// </summary>
        /// <param name="pos"></param>
        /// <param name="userId"></param>
        /// <param name="fbController"></param>
        /// <returns></returns>
        public async Task RemoveBelegChargenAsync(Belegposition pos, int userId, FbController2 fbController)
        {
            if (pos.PositionIstStückliste)
            {
                foreach (var subPos in pos.StücklistenArtikel)
                {
                    await RemoveBelegChargenAsync(subPos, userId, fbController);
                    subPos.Seriennummern.Clear();
                }
            }
            else if (pos.IstBundle)
            {
                foreach (var subPos in pos.BundleArtikel)
                {
                    await RemoveBelegChargenAsync(subPos, userId, fbController);
                    subPos.Seriennummern.Clear();
                }
            }
            else
            {
                if (pos.PositionBenötigtSeriennummer)
                {
                    await foreach (var seriennummer in BelegSeriennummer.GetBelegSeriennummernAsync(pos.PosId, pos.StücklistenPositionsId, fbController))
                    {
                        fbController.AddParameter("@INP_ID", seriennummer.BSNR_N_ID);
                        fbController.AddParameter("@INP_LASTUSER", userId);
                        await fbController.RunProcedureAsync("PROZ_BELESN_DEL");
                        if (pos.Gechargt > 0)
                        {
                            pos.Gechargt--;
                        }
                    }
                }
                else
                {
                    await foreach (Belegcharge charge in Belegcharge.GetPositionsChargenAsync(pos.PosId, pos.StücklistenPositionsId))
                    {
                        fbController.AddParameter("@INP_CHARGENID", charge.BCHA_N_ID);
                        await fbController.RunProcedureAsync("PROZ_BELEGCHARGEN_DEL");
                        pos.Gechargt = 0;
                    }
                }
            }
        }
        /// <summary>
        /// Chargt eine Belegposition in angegebener Menge für eine bestimmte Charge.
        /// </summary>
        /// <param name="pos"></param>
        /// <param name="chargenId"></param>
        /// <param name="menge"></param>
        /// <param name="userId"></param>
        /// <param name="fbController"></param>
        /// <returns>Liefert den EK-Preis für die Position in der Menge 1 zurück</returns>
        public async Task<decimal> ChargenAsync(Belegposition pos, int chargenId, decimal menge, int userId, FbController2 fbController)
        {
            fbController.AddParameter("@INP_CHARGE", chargenId);
            fbController.AddParameter("@INP_ARTIKEL", pos.Artikelnummer);
            fbController.AddParameter("@INP_GU", pos.BPOS_A_TYP);
            object? check = await fbController.FetchObjectAsync("select INHALT from PROZ_BELE_CHECK_CHARGE(@INP_CHARGE, @INP_ARTIKEL, @INP_GU)");

            if (check is null)
            {
                throw new ChargenException(pos.Artikelnummer, 0);
            }


            if (decimal.TryParse(check.ToString(), out decimal menge_offen))
            {
                if (menge_offen < menge)
                {
                    throw new ChargenException(pos.Artikelnummer, menge_offen);
                }
            }
            else
            {
                throw new ChargenException(pos.Artikelnummer, 0);
            }



            fbController.AddParameter("@INP_POSID", pos.PosId);
            fbController.AddParameter("@INP_STLPOSID", pos.StücklistenPositionsId);
            fbController.AddParameter("@INP_CHARGE", chargenId);
            fbController.AddParameter("@INP_MENGE", menge);
            decimal ek = Convert.ToDecimal(await fbController.FetchObjectAsync("execute procedure PROZ_BELEGCHARGEN_SAVE(@INP_POSID, @INP_STLPOSID, @INP_CHARGE, @INP_MENGE)"));
            return ek;
        }
        /// <summary>
        /// Chargt eine Seriennummer auf eine Belegposition
        /// </summary>
        /// <param name="seriennummer"></param>
        /// <param name="pos"></param>
        /// <param name="userId"></param>
        /// <param name="kundenname"></param>
        /// <param name="fbController"></param>
        /// <returns></returns>
        public async Task SeriennummerChargenAsync(Seriennummer seriennummer, Belegposition pos, int userId, string kundenname, FbController2 fbController)
        {
            fbController.AddParameter("@INP_SN", seriennummer.Nummer);
            fbController.AddParameter("@INP_CHARGE", seriennummer.Charge);
            fbController.AddParameter("@INP_ARTIKEL", seriennummer.Artikelnummer);
            fbController.AddParameter("@INP_LASTUSER", userId);
            fbController.AddParameter("@INP_BELEGPOSID", pos.PosId);
            fbController.AddParameter("@INP_BELEGNAME", kundenname.Length > 20 ? kundenname[..20] : kundenname);
            fbController.AddParameter("@INP_BELEGSTLID", pos.StücklistenPositionsId);
            await fbController.RunProcedureAsync("PROZ_SN_SAVE");
            pos.Seriennummern.Add(seriennummer);
            pos.Gechargt++;
        }
        /// <summary>
        /// Chargt eine Seriennummer auf eine Belegposition einer Gutschrift
        /// </summary>
        /// <param name="seriennummer"></param>
        /// <param name="pos"></param>
        /// <param name="userId"></param>
        /// <param name="kundenname"></param>
        /// <param name="fbController"></param>
        /// <returns></returns>
        public async Task SeriennummerChargenAsync(BelegSeriennummer seriennummer, Belegposition pos, int userId, string kundenname, FbController2 fbController)
        {
            fbController.AddParameter("@INP_SN", seriennummer.BSNR_A_SN);
            fbController.AddParameter("@INP_CHARGE", seriennummer.BSNR_N_CHARGE);
            fbController.AddParameter("@INP_ARTIKEL", seriennummer.Artikelnummer);
            fbController.AddParameter("@INP_LASTUSER", userId);
            fbController.AddParameter("@INP_BELEGPOSID", pos.PosId);
            fbController.AddParameter("@INP_BELEGNAME", kundenname.Length > 20 ? kundenname[..20] : kundenname);
            fbController.AddParameter("@INP_BELEGSTLID", pos.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 ?? string.Empty;
            if (meldung == string.Empty)
            {
                if (!String.IsNullOrWhiteSpace(seriennummer.BSNR_A_SN))
                {
                    Seriennummer? sn = await Seriennummer.GetSeriennummerAsync(seriennummer.BSNR_A_SN, seriennummer.BSNR_N_CHARGE);

                    if (sn is not null)
                    {
                        pos.Seriennummern.Add(sn);
                    }
                }

                pos.Gechargt++;
            }
            else
            {
                throw new ChargenException(seriennummer.Artikelnummer, 0, meldung);
            }
        }


    }
}
