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;
using WK5.Core.Basis.Filter;
using WK5.Core.Models;

namespace WK5.Core.Services
{
    /// <summary>
    /// Stellt alle Funktionen für die Zeiterfassung der WK5 zur Verfügung
    /// </summary>
    public class ZeiterfassungService : IModelService<Zeiterfassung, int>
    {
        /// <summary>
        /// Ruft eine bestimmte Zeiterfassung ab.
        /// </summary>
        /// <param name="identifier"></param>
        /// <param name="fbController"></param>
        /// <returns>Wenn für den Identifier keine Zeiterfassung gefunden wird, gibt diese Funktion null zurück.</returns>
        public async Task<Zeiterfassung?> GetAsync(int identifier, FbController2 fbController)
        {
            fbController.AddParameter("@ZEIT_N_NR", identifier);
            DataRow? row = await fbController.SelectRowAsync("SELECT * FROM ZEITEN WHERE ZEIT_N_NR = @ZEIT_N_NR");
            return row is null ? null : ObjectErweiterung.DataRowZuObjekt(new Zeiterfassung(), row);
        }
        /// <summary>
        /// Ruft für einen <see cref="ZeitenFilter"/> alle Zeiterfassungen ab.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="fbController"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async IAsyncEnumerable<Zeiterfassung> GetZeitenAsync(ZeitenFilter 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 Zeiterfassung(), row);
            }
        }
        /// <summary>
        /// Ruft die Übersicht der auszuwerten Zeiten ab.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="fbController"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async IAsyncEnumerable<(string kundennummer, string kundenname, long minuten)> GetAuswertungÜbersichtAsync(ZeitenFilter filter, FbController2 fbController, [EnumeratorCancellation] CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            DataTable data = await fbController.SelectDataAsync(filter.ToKundenQuery(fbController), cancellationToken);

            foreach (DataRow row in data.Rows)
            {
                cancellationToken.ThrowIfCancellationRequested();
                string kundennummer = row.Field<string>("ZEIT_A_KUNDENNR") ?? string.Empty;
                string kundenname = row.Field<string>("KUND_A_NAME1") ?? string.Empty;
                long minuten = row.Field<long>("MINUTEN");
                yield return (kundennummer, kundenname, minuten);
            }
        }
        /// <summary>
        /// Speichert eine neue Zeiterfassung
        /// </summary>
        /// <param name="input"></param>
        /// <param name="fbController"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public async Task CreateAsync(Zeiterfassung input, FbController2 fbController)
        {

            if (input.Start > input.Ende && input.Ende != default)
            {
                throw new ArgumentOutOfRangeException($"Das Startdatum kann nicht größer sein, als das Ende-Datum", nameof(input.Start));
            }

            input.AnlageUserId = fbController.UserId;
            input.LetzterBearbeiterUserId = fbController.UserId;
            input.LetzteÄnderung = DateTime.Now;

            fbController.AddParameter("@ZEIT_A_TYP", input.Typ);
            fbController.AddParameter("@ZEIT_N_BELEGNR", input.Belegnummer);
            fbController.AddParameter("@ZEIT_A_KUNDENNR", input.Kundennummer);
            fbController.AddParameter("@ZEIT_D_START", input.StartDatum);
            fbController.AddParameter("@ZEIT_T_START", input.StartZeit);
            fbController.AddParameter("@ZEIT_D_ENDE", input.EndeDatum);
            fbController.AddParameter("@ZEIT_T_ENDE", input.EndeZeit);
            fbController.AddParameter("@ZEIT_B_NOTIZ", input.Notiz);
            fbController.AddParameter("@ZEIT_L_BERECHNET", input.WirdBerechnet);
            fbController.AddParameter("@ZEIT_L_REMOTE", input.IstRemote);
            fbController.AddParameter("@ZEIT_N_KILOMETER", input.Kilometer);
            fbController.AddParameter("@ZEIT_N_ANLAGEUSER", input.AnlageUserId);
            fbController.AddParameter("@ZEIT_N_LASTUSER", input.LetzterBearbeiterUserId);
            fbController.AddParameter("@ZEIT_TIMESTAMP", input.LetzteÄnderung);
            input.Id = Convert.ToInt32(await fbController.FetchObjectAsync(@"INSERT INTO ZEITEN
(
ZEIT_A_TYP,
ZEIT_N_BELEGNR,
ZEIT_A_KUNDENNR,
ZEIT_D_START,
ZEIT_T_START,
ZEIT_D_ENDE,
ZEIT_T_ENDE,
ZEIT_B_NOTIZ,
ZEIT_L_BERECHNET,
ZEIT_L_REMOTE,
ZEIT_N_KILOMETER,
ZEIT_N_ANLAGEUSER,
ZEIT_N_LASTUSER,
ZEIT_TIMESTAMP
)
VALUES
(
@ZEIT_A_TYP,
@ZEIT_N_BELEGNR,
@ZEIT_A_KUNDENNR,
@ZEIT_D_START,
@ZEIT_T_START,
@ZEIT_D_ENDE,
@ZEIT_T_ENDE,
@ZEIT_B_NOTIZ,
@ZEIT_L_BERECHNET,
@ZEIT_L_REMOTE,
@ZEIT_N_KILOMETER,
@ZEIT_N_ANLAGEUSER,
@ZEIT_N_LASTUSER,
@ZEIT_TIMESTAMP
) 
RETURNING ZEIT_N_ID"));
        }
        /// <summary>
        /// Aktualisiert eine bestehende Zeiterfassung.
        /// </summary>
        /// <param name="input"></param>
        /// <param name="fbController"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public async Task UpdateAsync(Zeiterfassung input, FbController2 fbController)
        {
            if (input.Start > input.Ende && input.Ende != default)
            {
                throw new ArgumentOutOfRangeException($"Das Startdatum kann nicht größer sein, als das Ende-Datum", nameof(input.Start));
            }

            input.LetzteÄnderung = DateTime.Now;
            input.LetzterBearbeiterUserId = fbController.UserId;


            fbController.AddParameter("@ZEIT_A_TYP", input.Typ);
            fbController.AddParameter("@ZEIT_N_BELEGNR", input.Belegnummer);
            fbController.AddParameter("@ZEIT_A_KUNDENNR", input.Kundennummer);
            fbController.AddParameter("@ZEIT_D_START", input.StartDatum);
            fbController.AddParameter("@ZEIT_T_START", input.StartZeit);
            fbController.AddParameter("@ZEIT_D_ENDE", input.EndeDatum);
            fbController.AddParameter("@ZEIT_T_ENDE", input.EndeZeit);
            fbController.AddParameter("@ZEIT_B_NOTIZ", input.Notiz);
            fbController.AddParameter("@ZEIT_B_UNTERSCHRIFT", input.Unterschrift);
            fbController.AddParameter("@ZEIT_D_UNTERSCHRIFT", input.UnterschriftDatum);
            fbController.AddParameter("@ZEIT_A_UNTERSCHRIFT_NAME", input.UnterschriftName);
            fbController.AddParameter("@ZEIT_L_BERECHNET", input.WirdBerechnet);
            fbController.AddParameter("@ZEIT_L_REMOTE", input.IstRemote);
            fbController.AddParameter("@ZEIT_N_KILOMETER", input.Kilometer);
            fbController.AddParameter("@ZEIT_N_LASTUSER", input.LetzterBearbeiterUserId);
            fbController.AddParameter("@ZEIT_TIMESTAMP", input.LetzteÄnderung);
            fbController.AddParameter("@ZEIT_N_ID", input.Id);
            await fbController.QueryAsync(@"UPDATE ZEITEN SET
ZEIT_A_TYP = @ZEIT_A_TYP,
ZEIT_N_BELEGNR = @ZEIT_N_BELEGNR,
ZEIT_A_KUNDENNR = @ZEIT_A_KUNDENNR,
ZEIT_D_START = @ZEIT_D_START,
ZEIT_T_START = @ZEIT_T_START,
ZEIT_D_ENDE = @ZEIT_D_ENDE,
ZEIT_T_ENDE = @ZEIT_T_ENDE,
ZEIT_B_NOTIZ = @ZEIT_B_NOTIZ,
ZEIT_B_UNTERSCHRIFT = @ZEIT_B_UNTERSCHRIFT,
ZEIT_D_UNTERSCHRIFT = @ZEIT_D_UNTERSCHRIFT,
ZEIT_A_UNTERSCHRIFT_NAME = @ZEIT_A_UNTERSCHRIFT_NAME,
ZEIT_L_BERECHNET = @ZEIT_L_BERECHNET,
ZEIT_L_REMOTE = @ZEIT_L_REMOTE,
ZEIT_N_KILOMETER = @ZEIT_N_KILOMETER,
ZEIT_N_LASTUSER = @ZEIT_N_LASTUSER,
ZEIT_TIMESTAMP = @ZEIT_TIMESTAMP
WHERE ZEIT_N_ID = @ZEIT_N_ID");
        }
        /// <summary>
        /// Ruft die derzeit offene Zeiterfassung eine Mitarbeiters ab.
        /// <para>
        /// Ein Mitarbeiter kann immer nur eine Zeiterfassung gleichzeitig laufen haben.
        /// </para>
        /// </summary>
        /// <param name="mitarbeiterId"></param>
        /// <param name="fbController"></param>
        /// <returns>Wenn der Mitarbeiter keine offene Zeiterfassung hat, liefert diese Funktion null zurück.</returns>
        public async Task<Zeiterfassung?> GetAktiveZeiterfassungVonMitarbeiter(int mitarbeiterId, FbController2 fbController)
        {
            fbController.AddParameter("@ZEIT_N_ANLAGEUSER", mitarbeiterId);
            DataRow? row = await fbController.SelectRowAsync("SELECT FIRST 1 * FROM ZEITEN WHERE ZEIT_N_ANLAGEUSER = @ZEIT_N_ANLAGEUSER AND ZEIT_D_ENDE IS NULL OR ZEIT_D_ENDE = '01.01.0001'");
            return row is null ? null : ObjectErweiterung.DataRowZuObjekt(new Zeiterfassung(), row);
        }
        /// <summary>
        /// Setzt alle Zeiten für von einem bestimmten Zeitraum auf berechnet.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="kundennummer"></param>
        /// <param name="fbController"></param>
        /// <returns></returns>
        public async Task ZeitenAbrechnenAsync(ZeitenFilter filter, string kundennummer, FbController2 fbController)
        {
            StringBuilder sqlBuilder = new StringBuilder();
            sqlBuilder.AppendLine(@"UPDATE ZEITEN SET ZEIT_L_ABGERECHNET = 'Y' WHERE 
ZEIT_A_KUNDENNR = @KUNDENNUMMER
AND ZEIT_D_ENDE IS NOT NULL AND ZEIT_D_ENDE <> '01.01.0001' 
AND ZEIT_T_ENDE IS NOT NULL AND ZEIT_T_ENDE <> '00:00:00'
AND ZEIT_D_START >= CAST(@STARTDATUM as DATE) AND ZEIT_D_ENDE <= CAST(@ENDDATUM AS DATE)
");
            if (filter.AusschließlichZeitenDieBerechnetWerden)
            {
                sqlBuilder.AppendLine(" AND ZEIT_L_BERECHNET = 'Y'");
            }


            fbController.AddParameter("@KUNDENNUMMER", kundennummer);
            fbController.AddParameter("@STARTDATUM", filter.Zeitraum.Von);
            fbController.AddParameter("@ENDDATUM", filter.Zeitraum.Bis);

            await fbController.QueryAsync(sqlBuilder.ToString());
        }
    }
}
