using KarleyLibrary.Erweiterungen;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WK5.Core.Basis.Filter;
using WK5.Core.Basis.Filter.Statistik;
using WK5.Core.Models.Statistik;
using WK5.Core.Models.Statistik.Umsatzstatistik;

namespace WK5.Core.Services
{
    public class StatistikService
    {
        /// <summary>
        /// Lädt sich alle Kunden die seit dem spezifizierten Datum kein verbrauchsmaterial des Artikel mehr gekauft haben
        /// </summary>
        /// <param name="datum">Das Datum ab dem die Kunden gesucht werden sollen</param>
        /// <param name="artikel">Der Artikel für den die Kunden gesucht werden sollen</param>
        /// <returns>Gibt einen <see cref="Task"/> of <see cref="List"/> of <see cref="KundenOhneVerbrauchsmaterialResult"/> zurück</returns>
        public async Task<List<KundenOhneVerbrauchsmaterialResult>> KundenOhneVerbrauchsmaterialAsync(DateTime datum, string artikel)
        {
            using FbController2 _fbController = new FbController2();
            List<KundenOhneVerbrauchsmaterialResult> result = new List<KundenOhneVerbrauchsmaterialResult>();

            string kundenSql = @"SELECT DISTINCT K.KUND_A_NR AS KUNDNR FROM KUNDEN K 
                INNER JOIN BELEGE B ON (B.BELE_A_KUNDENNR = K.KUND_A_NR)
                INNER JOIN BELEGPOS BP ON (BP.BPOS_N_NR = B.BELE_N_NR AND BP.BPOS_A_TYP = 'RE') 
                WHERE B.BELE_A_TYP = 'RE' AND BP.BPOS_A_ARTIKELNR = @HAUPTARTIKEL
                AND CAST(B.BELE_D_DATE AS DATE) <= @DATUM
                AND (K.KUND_L_SPERRE = 'N' AND K.KUND_WK5_L_KEINMAILING = 'N') ORDER BY B.BELE_D_DATE DESC";

            _fbController.AddParameter("@DATUM", datum.ToShortDateString());
            _fbController.AddParameter("@HAUPTARTIKEL", artikel);
            DataTable kunden = await _fbController.SelectDataAsync(kundenSql);


            if (kunden.Rows.Count > 0)
            {
                string kdNummern = "'" + String.Join("','", kunden.AsEnumerable().Select(x => x["KUNDNR"].ToString())) + "'";
                string belegDatumSql = @"SELECT DISTINCT BELE_A_KUNDENNR, MAX(BELE_D_DATE) AS BELE_D_DATE FROM BELEGE B 
                            INNER JOIN BELEGPOS BP ON (BP.BPOS_N_NR = B.BELE_N_NR AND BP.BPOS_A_TYP = 'RE') 
                            WHERE B.BELE_A_KUNDENNR IN (" + kdNummern + @")  
                            AND B.BELE_A_TYP = 'RE' 
                            AND BP.BPOS_A_ARTIKELNR IN (select ARZU_A_ZUBEHOERART from ARTIKELZUBEHOER where ARZU_A_AUSGANGSART = @AUSGANGSARTIKEL)
                            GROUP BY BELE_A_KUNDENNR";

                _fbController.AddParameter("@AUSGANGSARTIKEL", artikel);
                DataTable belegDatum = await _fbController.SelectDataAsync(belegDatumSql);

                if (belegDatum.Rows.Count > 0)
                {
                    IEnumerable<DataRow> filtered = belegDatum.AsEnumerable().Where(x => DateTime.TryParse(x["BELE_D_DATE"].ToString(), out DateTime tmpDate) ? tmpDate <= datum : false);
                    kdNummern = "'" + String.Join("','", filtered.Select(x => x["BELE_A_KUNDENNR"])) + "'";

                    string zuberhörSql = @"SELECT BELE_A_KUNDENNR, UPPER(BELE_A_NAME1) AS BELE_A_NAME1, MAX(BELE_D_DATE) AS BELE_D_DATE FROM BELEGE B 
                                    INNER JOIN BELEGPOS BP ON (BP.BPOS_N_NR = B.BELE_N_NR AND BP.BPOS_A_TYP = 'RE') 
                                    WHERE B.BELE_A_KUNDENNR IN (" + kdNummern + @")  
                                    AND B.BELE_A_TYP = 'RE' 
                                    AND BP.BPOS_A_ARTIKELNR IN (select ARZU_A_ZUBEHOERART from ARTIKELZUBEHOER where ARZU_A_AUSGANGSART = @AUSGANGSARTIKEL) GROUP BY BELE_A_KUNDENNR, BELE_A_NAME1
                                    ORDER BY BELE_A_KUNDENNR";
                    _fbController.AddParameter("@AUSGANGSARTIKEL", artikel);

                    DataTable tmpResult = await _fbController.SelectDataAsync(zuberhörSql);

                    foreach (DataRow row in tmpResult.Rows)
                    {

                        result.Add(new KundenOhneVerbrauchsmaterialResult
                        {
                            BELE_A_KUNDENNR = row["BELE_A_KUNDENNR"] as string ?? string.Empty,
                            BELE_A_NAME1 = row["BELE_A_NAME1"] as string ?? string.Empty,
                            BELE_D_DATE = row.Field<DateTime>("BELE_D_DATE"),
                        });
                    }
                }
            }


            return result;

        }

        /// <summary>
        /// Sucht Artikel Nach Namen mit Verkaufsstatistik
        /// </summary>
        /// <param name="from">Ab welchem Datum gesucht werden soll</param>
        /// <param name="to">Bis zu welchem Datum gesucht werden soll</param>
        /// <param name="suche">Der Begriff nach dem gesucht werden soll</param>
        /// <param name="belegTyp">Der Belegtyp in dem der Artikel gesucht werden soll</param>
        /// <returns>Gibt einen <see cref="Task"/> of <see cref="List"/> of <see cref="ArtikelNachNameResult"/> zurück</returns>
        public async Task<List<ArtikelNachNameResult>> ArtikelNachNameAsync(DateTime from, DateTime to, string suche, BelegTyp belegTyp)
        {
            using FbController2 _fbController = new FbController2();
            List<ArtikelNachNameResult> result = new List<ArtikelNachNameResult>();

            string sql = $@"SELECT DISTINCT BPOS_A_ARTIKELNR 
FROM BELEGPOS 
WHERE UPPER(BPOS_A_ARTIKELNR) LIKE '%{suche}%' 
OR UPPER(BPOS_A_BEZ1) LIKE '%{suche}%'";

            //fbController.AddParameter("@KEYWORD", $"%{suche}%");
            DataTable data = await _fbController.SelectDataAsync(sql);


            foreach (DataRow row in data.Rows)
            {
                string dataSql = @"SELECT FIRST 1
                    (SELECT SUM(BPOS_N_MENGE) 
                    FROM BELEGPOS                     
                    WHERE BPOS_A_ARTIKELNR = @BPOS_A_ARTIKELNR 
                    AND BPOS_A_TYP = @BPOS_A_TYP 
                    AND BPOS_TIMESTAMP > @BPOS_TIMESTAMP_FROM 
                    AND BPOS_TIMESTAMP < @BPOS_TIMESTAMP_TO 
                    AND BPOS_A_ARTIKELNR != 'DIVERS'  ) as total,   
                    (SELECT SUM(BPOS_N_MENGE) 
                    FROM BELEGPOS                     
                    WHERE BPOS_A_ARTIKELNR = @BPOS_A_ARTIKELNR 
                    AND BPOS_A_TYP = 'GU'
                    AND BPOS_TIMESTAMP > @BPOS_TIMESTAMP_FROM 
                    AND BPOS_TIMESTAMP < @BPOS_TIMESTAMP_TO 
                    AND BPOS_A_ARTIKELNR != 'DIVERS'  ) as gutschrift,
                    BPOS_TIMESTAMP, ARTI_A_BEZ1
                    FROM BELEGPOS 
                    LEFT JOIN ARTIKEL ON ARTI_A_NR = BPOS_A_ARTIKELNR
                    WHERE BPOS_A_ARTIKELNR = @BPOS_A_ARTIKELNR 
                    AND BPOS_A_TYP = @BPOS_A_TYP 
                    AND BPOS_TIMESTAMP > @BPOS_TIMESTAMP_FROM 
                    AND BPOS_TIMESTAMP < @BPOS_TIMESTAMP_TO 
                    AND BPOS_A_ARTIKELNR != 'DIVERS'";

                _fbController.AddParameter("@BPOS_A_ARTIKELNR", row["BPOS_A_ARTIKELNR"] as string);
                _fbController.AddParameter("@BPOS_A_TYP", EnumHelper.GetBelegTypString(belegTyp));
                _fbController.AddParameter("@BPOS_TIMESTAMP_FROM", from.ToString("dd.MM.yyyy"));
                _fbController.AddParameter("@BPOS_TIMESTAMP_TO", to.ToString("dd.MM.yyyy"));

                DataTable tmpData = await _fbController.SelectDataAsync(dataSql);

                foreach (DataRow tRow in tmpData.Rows)
                {
                    _ = float.TryParse(tRow["total"].ToString(), out float total);
                    _ = float.TryParse(tRow["gutschrift"].ToString(), out float gutschrift);
                    _ = DateTime.TryParse(tRow["BPOS_TIMESTAMP"].ToString(), out DateTime zuletztGekauft);

                    result.Add(new ArtikelNachNameResult
                    {
                        Artikelnummer = row["BPOS_A_ARTIKELNR"] as string,
                        Bezeichnung = tRow["ARTI_A_BEZ1"] as string,
                        GesamtVerkauft = total-gutschrift,
                        Typ = belegTyp,
                        ZuletztGekauft = zuletztGekauft

                    });
                }
            }

            return result;
        }

        /// <summary>
        /// Lädt die Verkaufsstatistik für alle Artikel einer Warengruppe
        /// </summary>
        /// <param name="from">Ab welchem Datum die Artikel geladen werden sollen</param>
        /// <param name="to">Bis zu welchem Datum die Artikel geladen werden sollen</param>
        /// <param name="warengruppe">Die Warengruppe aus der sich die Artikel geladen werden sollen</param>
        /// <param name="unterwarengruppe">Die Unterwarengruppe aus der sich die Artikel geladen werden sollen</param>
        /// <param name="limit">Wie viele Artikel sich maximal geladen werden sollen</param>
        /// <returns>Gibt ein <see cref="IAsyncEnumerable{T}"/> of <see cref="VerkaufNachWarengruppeResult"/> zurück</returns>
        public async IAsyncEnumerable<VerkaufNachWarengruppeResult> VerkaufNachWarengruppe(DateTime from, DateTime to, string warengruppe, string unterwarengruppe, int limit)
        {
            using FbController2 _fbController = new FbController2();
            StringBuilder sb = new StringBuilder();


            sb.AppendLine($@"SELECT (SELECT SUM(BPOS_N_MENGE) FROM BELEGPOS INNER JOIN BELEGE ON BPOS_N_NR = BELE_N_NR AND BPOS_A_TYP = BELE_A_TYP  WHERE BPOS_A_ARTIKELNR = ARTI_A_NR AND BPOS_A_TYP = 'RE' AND CAST(BELE_D_DATE AS DATE) BETWEEN @FROM AND @TO) as MENGE, (SELECT SUM(BPOS_N_MENGE) FROM BELEGPOS INNER JOIN BELEGE ON BPOS_N_NR = BELE_N_NR AND BPOS_A_TYP = BELE_A_TYP  WHERE BPOS_A_ARTIKELNR = ARTI_A_NR AND BPOS_A_TYP = 'GU' AND CAST(BELE_D_DATE AS DATE) BETWEEN @FROM AND @TO) as GUTSCHRIFT, ARTI_A_NR, ARTI_A_BEZ1 FROM ARTIKEL
WHERE ARTI_A_WARENGRUPPE = @WARENGRUPPE ");
            _fbController.AddParameter("@WARENGRUPPE", warengruppe);
            _fbController.AddParameter("@FROM", from);
            _fbController.AddParameter("@TO", to);


            if (!String.IsNullOrWhiteSpace(unterwarengruppe))
            {
                sb.AppendLine(" AND ARTI_A_UNTERWARENG = @UNTERWARENGRUPPE ");
                _fbController.AddParameter("@UNTERWARENGRUPPE", unterwarengruppe);
            }
            sb.AppendLine(" ORDER BY MENGE DESC ROWS @LIMIT");
            _fbController.AddParameter("@LIMIT", limit);
            string query = sb.ToString();
            DataTable data = await _fbController.SelectDataAsync(sb.ToString());

            foreach (DataRow row in data.Rows)
            {
                float.TryParse(row["MENGE"].ToString(), out float menge);
                float.TryParse(row["GUTSCHRIFT"].ToString(), out float gutschrift);
                yield return new VerkaufNachWarengruppeResult
                {
                    Artikelnummer = row.Field<string>("ARTI_A_NR") ?? string.Empty,
                    Beschreibung = row.Field<string>("ARTI_A_BEZ1") ?? string.Empty,
                    Menge = menge-gutschrift
                };
            }
        }

        public async IAsyncEnumerable<KundenMitVerbrauchsmaterialResult> KundenMitVerbrauchsmaterialsAsync(DateTime datum, string artikel)
        {
            using FbController2 fbController = new FbController2();
            fbController.AddParameter("@DATUM", datum);
            fbController.AddParameter("@ARTIKEL", artikel);

            string sql = @"SELECT DISTINCT KUND_A_NR, KUND_A_NAME1, KUND_A_NAME2, KUND_A_NAME3 FROM BELEGPOS
LEFT JOIN BELEGE ON BELE_A_TYP = BPOS_A_TYP AND BELE_N_NR = BPOS_N_NR
LEFT JOIN KUNDEN KU ON KUND_A_NR = BELE_A_KUNDENNR
WHERE 
BPOS_A_ARTIKELNR IN (
SELECT ARZU_A_ZUBEHOERART FROM ARTIKELZUBEHOER 
LEFT JOIN ARTIKEL ON ARZU_A_ZUBEHOERART = ARTI_A_NR
WHERE ARZU_A_AUSGANGSART = @ARTIKEL
AND ARTI_L_DIENSTLEIST = 'N'
AND ARTI_L_INAKTIV = 'N'
)
AND BPOS_A_ARTIKELNR != @ARTIKEL
AND BPOS_A_TYP = 'RE' AND BPOS_D_LIEFERTERMIN > @DATUM
AND (SELECT COUNT(*) FROM BELEGPOS BPOS2 
LEFT JOIN BELEGE BE2 ON BE2.BELE_A_TYP = BPOS2.BPOS_A_TYP AND BE2.BELE_N_NR = BPOS2.BPOS_N_NR
LEFT JOIN KUNDEN KU2 ON KU2.KUND_A_NR = BE2.BELE_A_KUNDENNR
LEFT JOIN ARTIKEL ARTI ON ARTI_A_NR = BPOS_A_ARTIKELNR
WHERE KU2.KUND_A_NR = KU.KUND_A_NR
AND BPOS_A_TYP = 'RE'
AND BPOS_A_ARTIKELNR = @ARTIKEL) <= 0 ";
            DataTable data = await fbController.SelectDataAsync(sql);
            foreach (DataRow row in data.Rows)
            {
                if (row is not null)
                {
                    yield return ObjectErweiterung.DataRowZuObjekt(new KundenMitVerbrauchsmaterialResult(), row);
                }
            }
        }
    
        public async IAsyncEnumerable<(string artikelnummer, decimal gekauft)> ArtikelNachKundeAsync(string kundennummer, Zeitraum zeitraum, List<string> artikelnummern, FbController2 fbController, [EnumeratorCancellation] CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            string sql = @"
SELECT BP.BPOS_A_ARTIKELNR AS ARTIKELNUMMER, SUM(CASE WHEN BPOS_A_TYP = 'GU' THEN -BP.BPOS_N_MENGE else BP.BPOS_N_MENGE END) AS GEKAUFT
FROM BELEGE B
LEFT JOIN BELEGPOS BP ON (BP.BPOS_A_TYP = BELE_A_TYP AND BP.BPOS_N_NR = B.BELE_N_NR)
WHERE BELE_A_KUNDENNR = @KUNDENNUMMER AND BELE_D_DATE BETWEEN @VON AND @BIS
AND BP.BPOS_A_ARTIKELNR IN ('" + String.Join("','", artikelnummern) + @"') AND (B.BELE_A_TYP = 'RE' OR B.BELE_A_TYP = 'GU')
GROUP BY BP.BPOS_A_ARTIKELNR";

            fbController.AddParameter("@VON", zeitraum.Von);
            fbController.AddParameter("@BIS", zeitraum.Bis);
            fbController.AddParameter("@KUNDENNUMMER", kundennummer);

            DataTable data = await fbController.SelectDataAsync(sql);

            foreach (DataRow row in data.Rows)
            {
                cancellationToken.ThrowIfCancellationRequested();
                string artikelnummer = row.Field<string>("ARTIKELNUMMER") ?? throw new ArgumentNullException(nameof(artikelnummer));
                decimal gekauf = row.Field<decimal>("GEKAUFT");
                yield return (artikelnummer, gekauf);
            }
        }
    
        public async Task<decimal> GetBestandswertAsync(DateTime datum, FbController2 fbController, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();

            fbController.AddParameter("@DATUM", datum);
            decimal bestandswert = Convert.ToDecimal(await fbController.FetchObjectAsync("SELECT BESTANDSWERT FROM ARTIKELBESTAND_DATUM_GES(@DATUM, '', '')", cancellationToken));

            return bestandswert;
        }

        public async IAsyncEnumerable<LieferantenUmsatzResult> GetLieferantenUmsatzAsync(LieferantenUmsatzFilter filter, FbController2 fbController, [EnumeratorCancellation] CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            DataTable data = await fbController.SelectDataAsync(filter.ToSqlQuery(fbController), cancellationToken);

            foreach (DataRow row in data.Rows)
            {
                cancellationToken.ThrowIfCancellationRequested();
                yield return ObjectErweiterung.DataRowZuObjekt(new LieferantenUmsatzResult(), row);
            }
        }

        public async IAsyncEnumerable<KundenUmsatzResult> GetKundenUmsatzAsync(KundenUmsatzFilter filter, FbController2 fbController, [EnumeratorCancellation] CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            DataTable data = await fbController.SelectDataAsync(filter.ToSqlQuery(fbController), cancellationToken);

            foreach (DataRow row in data.Rows)
            {
                cancellationToken.ThrowIfCancellationRequested();
                yield return ObjectErweiterung.DataRowZuObjekt(new KundenUmsatzResult(), row);
            }
        }

        public async IAsyncEnumerable<ArtikelUmsatzResult> GetArtikelUmsatzAsync(ArtikelUmsatzFilter filter, FbController2 fbController, [EnumeratorCancellation] CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            DataTable data = await fbController.SelectDataAsync(filter.ToSqlQuery(fbController), cancellationToken);

            foreach (DataRow row in data.Rows)
            {
                cancellationToken.ThrowIfCancellationRequested();
                yield return ObjectErweiterung.DataRowZuObjekt(new ArtikelUmsatzResult(), row);
            }
        }

        public async Task<decimal> GetGesamtUmsatzAsync(GesamtUmsatzFilter filter, FbController2 fbController, CancellationToken token = default)
        {
            object? ergebnis = await fbController.FetchObjectAsync(filter.ToSqlQuery(fbController), token);
            if(ergebnis is null) { return 0m; };
            decimal.TryParse(ergebnis.ToString(), out decimal wert);
            return wert;


        }


        public async IAsyncEnumerable<TopArtikelNachLieferantResult> GetTopArtikelNachLieferantAsync(TopArtikelNachLieferantFilter filter, FbController2 fbController, [EnumeratorCancellation] CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            DataTable data = await fbController.SelectDataAsync(filter.ToSqlQuery(fbController), cancellationToken);

            foreach (DataRow row in data.Rows)
            {
                cancellationToken.ThrowIfCancellationRequested();
                yield return ObjectErweiterung.DataRowZuObjekt(new TopArtikelNachLieferantResult(), row);
            }
        }
    
        public async IAsyncEnumerable<AbgelehnteAngeboteResult> GetAbgelehnteAngeboteAsync(AbgelehnteAngeboteFilter filter, FbController2 fbController, [EnumeratorCancellation] CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();

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

            foreach (DataRow row in data.Rows)
            {
                cancellationToken.ThrowIfCancellationRequested();
                yield return ObjectErweiterung.DataRowZuObjekt(new AbgelehnteAngeboteResult(), row);
            }
        }
    
    
        public Task<List<MitarbeiterauswertungResult>> GetMitarbeiterauswertungAsync(MitarbeiterStatistikFilter filter, FbController2 fbController)
        {
            return fbController.SelectDataAsync<MitarbeiterauswertungResult>(filter.ToSqlQuery(fbController), new
            {
                TYP = filter.Typ,
                MITARBEITER_ID = filter.MitarbeiterId,
                FROM = filter.From,
                TO = filter.To
            });
        }

        public Task<List<EARStatistikResult>> GetEARStatistik(EARStatistikFilter filter, FbController2 fbController)
        {            
            return fbController.SelectDataAsync<EARStatistikResult>(filter.ToSqlQuery(fbController), new
            {                
                FROM = filter.From,
                TO = filter.To                
            });            
        }
    }
}

