using Elwig.Helpers;
using Elwig.Helpers.Export;
using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;

namespace Elwig.Services {
    public static class SyncService {

        public static readonly Expression<Func<Member, bool>> ChangedMembers = (m) => ((m.XTime == null && m.MTime > 1751328000) || m.MTime > m.XTime) && (m.ITime == null || m.MTime > m.ITime);
        public static readonly Expression<Func<Delivery, bool>> ChangedDeliveries = (d) => ((d.XTime == null && d.MTime > 1751328000) || d.MTime > d.XTime) && (d.ITime == null || d.MTime > d.ITime);

        public static async Task Upload(string url, string username, string password, IQueryable<Member> query, IEnumerable<string> filterNames) {
            try {
                var filename = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip";
                var path = Path.Combine(App.TempPath, filename);
                var members = await query
                    .OrderBy(m => m.MgNr)
                    .Include(m => m.BillingAddress)
                    .Include(m => m.TelephoneNumbers)
                    .Include(m => m.EmailAddresses)
                    .Include(m => m.DefaultWbKg!.Gl)
                    .AsSplitQuery()
                    .ToListAsync();
                var areaComs = await query
                    .SelectMany(m => m.AreaCommitments)
                    .OrderBy(c => c.MgNr).ThenBy(c => c.FbNr)
                    .Include(c => c.Rd)
                    .Include(c => c.Kg.Gl)
                    .ToListAsync();
                var wbKgs = members
                    .Where(m => m.DefaultWbKg != null)
                    .Select(m => m.DefaultWbKg!)
                    .Union(areaComs.Select(c => c.Kg))
                    .Distinct()
                    .OrderBy(k => k.KgNr)
                    .ToList();
                if (members.Count == 0) {
                    MessageBox.Show("Es wurden keine Mitglieder zum Hochladen ausgewählt!", "Mitglieder hochladen",
                        MessageBoxButton.OK, MessageBoxImage.Error);
                } else {
                    var exportedAt = DateTime.Now;
                    await ElwigData.Export(path, members, areaComs, wbKgs, filterNames);
                    await Utils.UploadExportData(path, url, username, password);
                    await UpdateExportedAt(members, [], exportedAt);
                    MessageBox.Show($"Hochladen von {members.Count:N0} Mitgliedern erfolgreich!", "Mitglieder hochgeladen",
                        MessageBoxButton.OK, MessageBoxImage.Information);
                }
            } catch (HttpRequestException exc) {
                MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
            } catch (TaskCanceledException exc) {
                MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
            } catch (Exception exc) {
                MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        public static async Task Upload(string url, string username, string password, IQueryable<DeliveryPart> query, IEnumerable<string> filterNames) {
            try {
                var filename = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip";
                var path = Path.Combine(App.TempPath, filename);
                var list = await query
                    .Select(p => p.Delivery)
                    .Distinct()
                    .Include(d => d.Parts).ThenInclude(p => p.PartModifiers).ThenInclude(m => m.Modifier)
                    .Include(d => d.Parts).ThenInclude(p => p.Rd)
                    .Include(d => d.Parts).ThenInclude(p => p.Kg!.Gl)
                    .AsSplitQuery()
                    .ToListAsync();
                var wbKgs = list
                    .SelectMany(d => d.Parts)
                    .Where(p => p.Kg != null)
                    .Select(p => p.Kg!)
                    .DistinctBy(k => k.KgNr)
                    .OrderBy(k => k.KgNr)
                    .ToList();
                if (list.Count == 0) {
                    MessageBox.Show("Es wurden keine Lieferungen zum Hochladen ausgewählt!", "Lieferungen hochladen",
                        MessageBoxButton.OK, MessageBoxImage.Error);
                } else {
                    var exportedAt = DateTime.Now;
                    await ElwigData.Export(path, list, wbKgs, filterNames);
                    await Utils.UploadExportData(path, url, username, password);
                    await UpdateExportedAt([], list, exportedAt);
                    MessageBox.Show($"Hochladen von {list.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochgeladen",
                        MessageBoxButton.OK, MessageBoxImage.Information);
                }
            } catch (HttpRequestException exc) {
                MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
            } catch (TaskCanceledException exc) {
                MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
            } catch (Exception exc) {
                MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        public static async Task UploadModified(string url, string username, string password) {
            try {
                var path = Path.Combine(App.TempPath, $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip");
                List<Member> members;
                List<AreaCom> areaComs;
                List<Delivery> deliveries;
                using (var ctx = new AppDbContext()) {
                    members = await ctx.Members
                        .Where(ChangedMembers)
                        .Include(m => m.BillingAddress)
                        .Include(m => m.TelephoneNumbers)
                        .Include(m => m.EmailAddresses)
                        .Include(m => m.DefaultWbKg!.Gl)
                        .OrderBy(m => m.MgNr)
                        .AsSplitQuery()
                        .ToListAsync();
                    areaComs = await ctx.Members
                       .Where(ChangedMembers)
                       .SelectMany(m => m.AreaCommitments)
                       .Include(c => c.Rd)
                       .Include(c => c.Kg.Gl)
                       .OrderBy(c => c.MgNr).ThenBy(c => c.FbNr)
                       .ToListAsync();
                    deliveries = await ctx.Deliveries
                        .Where(ChangedDeliveries)
                        .Include(d => d.Parts).ThenInclude(p => p.PartModifiers).ThenInclude(m => m.Modifier)
                        .Include(d => d.Parts).ThenInclude(p => p.Rd)
                        .Include(d => d.Parts).ThenInclude(p => p.Kg).ThenInclude(k => k!.Gl)
                        .OrderBy(d => d.DateString).ThenBy(d => d.TimeString).ThenBy(d => d.LsNr)
                        .AsSplitQuery()
                        .ToListAsync();
                }
                var wbKgs = members
                    .Where(m => m.DefaultWbKg != null)
                    .Select(m => m.DefaultWbKg!)
                    .Union(areaComs.Select(c => c.Kg))
                    .Union(deliveries.SelectMany(d => d.Parts)
                        .Where(p => p.Kg != null)
                        .Select(p => p.Kg!))
                    .DistinctBy(k => k.KgNr)
                    .OrderBy(k => k.KgNr)
                    .ToList();
                if (members.Count == 0 && deliveries.Count == 0) {
                    MessageBox.Show("Es gibt keine geänderten Mitglieder oder Lieferungen, die hochgeladen werden könnten!", "Mitglieder und Lieferungen hochladen",
                        MessageBoxButton.OK, MessageBoxImage.Information);
                } else {
                    var exportedAt = DateTime.Now;
                    await (new ElwigData.ElwigExport {
                        Members = (members, ["geändert seit letztem Export"]),
                        AreaComs = (areaComs, ["von exportierten Mitgliedern"]),
                        Deliveries = (deliveries, ["geändert seit letzem Export"]),
                        WbKgs = (wbKgs, ["von exportierten Mitgliedern, Flächenbindungen und Lieferungen"]),
                    }).Export(path);
                    await Utils.UploadExportData(path, url, username, password);
                    await UpdateExportedAt(members, deliveries, exportedAt);
                    MessageBox.Show($"Hochladen von {members.Count:N0} Mitgliedern und {deliveries.Count:N0} Lieferungen erfolgreich!", "Mitglieder und Lieferungen hochladen",
                        MessageBoxButton.OK, MessageBoxImage.Information);
                }
            } catch (HttpRequestException exc) {
                MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder und Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
            } catch (TaskCanceledException exc) {
                MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder und Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
            } catch (Exception exc) {
                MessageBox.Show(exc.Message, "Mitglieder und Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        public static async Task UploadBranchDeliveries(string url, string username, string password, int? year = null) {
            try {
                year ??= Utils.CurrentLastSeason;
                var path = Path.Combine(App.TempPath, $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip");
                using var ctx = new AppDbContext();
                var deliveries = await ctx.Deliveries
                    .Where(d => d.Year == year && d.ZwstId == App.ZwstId)
                    .Include(d => d.Parts).ThenInclude(p => p.PartModifiers).ThenInclude(m => m.Modifier)
                    .Include(d => d.Parts).ThenInclude(p => p.Rd)
                    .Include(d => d.Parts).ThenInclude(p => p.Kg).ThenInclude(k => k!.Gl)
                    .OrderBy(d => d.DateString).ThenBy(d => d.TimeString).ThenBy(d => d.LsNr)
                    .AsSplitQuery()
                    .ToListAsync();
                var wbKgs = deliveries
                    .SelectMany(d => d.Parts)
                    .Where(p => p.Kg != null)
                    .Select(p => p.Kg!)
                    .DistinctBy(k => k.KgNr)
                    .ToList();
                if (deliveries.Count == 0) {
                    MessageBox.Show("Es gibt keine Lieferungen, die hochgeladen werden können!", "Lieferungen hochladen",
                        MessageBoxButton.OK, MessageBoxImage.Error);
                } else {
                    var exportedAt = DateTime.Now;
                    await ElwigData.Export(path, deliveries, wbKgs, [$"{year}", $"Zweigstelle {App.BranchName}"]);
                    await Utils.UploadExportData(path, url, username, password);
                    await UpdateExportedAt([], deliveries, exportedAt);
                    MessageBox.Show($"Hochladen von {deliveries.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochladen",
                        MessageBoxButton.OK, MessageBoxImage.Information);
                }
            } catch (HttpRequestException exc) {
                MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
            } catch (TaskCanceledException exc) {
                MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
            } catch (Exception exc) {
                MessageBox.Show(exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        public static async Task<List<(string Name, string Url)>> GetFilesToImport(string url, string username, string password) {
            var data = await Utils.GetExportMetaData(url, username, password);
            var files = data
                .Select(f => new {
                    Name = f!["name"]!.AsValue().GetValue<string>(),
                    Timestamp = f!["timestamp"] != null && DateTime.TryParseExact(f!["timestamp"]!.AsValue().GetValue<string>(), "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? dt : (DateTime?)null,
                    ZwstId = f!["meta"]?["zwstid"]?.AsValue().GetValue<string>() ?? f!["zwstid"]?.AsValue().GetValue<string>(),
                    Device = f!["meta"]?["device"]?.AsValue().GetValue<string>(),
                    Url = f!["url"]!.AsValue().GetValue<string>(),
                    Size = f!["size"]!.AsValue().GetValue<long>(),
                })
                .Where(f => f.Timestamp >= new DateTime(Utils.CurrentLastSeason, 7, 1))
                .ToList();

            var imported = await ElwigData.GetImportedFiles();
            return [.. files
                .Where(f => f.Device != Environment.MachineName && !imported.Contains(f.Name))
                .Select(f => (f.Name, f.Url))
            ];
        }

        public static async Task Download(string url, string username, string password) {
            try {
                var import = await GetFilesToImport(url, username, password);
                var paths = new List<string>();
                using (var client = Utils.GetHttpClient(username, password)) {
                    foreach (var f in import) {
                        var filename = Path.Combine(App.TempPath, f.Name);
                        using var stream = new FileStream(filename, FileMode.Create);
                        await client.DownloadAsync(f.Url, stream);
                        paths.Add(filename);
                    }
                }
                await ElwigData.Import(paths, ElwigData.ImportMode.FromBranches);
            } catch (HttpRequestException exc) {
                MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
            } catch (TaskCanceledException exc) {
                MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
            } catch (Exception exc) {
                MessageBox.Show(exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        private static async Task UpdateExportedAt(IEnumerable<Member> member, IEnumerable<Delivery> deliveries, DateTime dateTime) {
            var timestamp = ((DateTimeOffset)dateTime.ToUniversalTime()).ToUnixTimeSeconds();
            var mgnrs = string.Join(",", member.Select(m => $"{m.MgNr}").Append("0"));
            var dids = string.Join(",", deliveries.Select(d => $"({d.Year},{d.DId})").Append("(0,0)"));
            using (var cnx = await AppDbContext.ConnectAsync()) {
                await cnx.ExecuteBatch($"""
                    BEGIN;
                    UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_TIME_TRIGGERS';
                    UPDATE member           SET xtime = {timestamp} WHERE mgnr IN ({mgnrs});
                    UPDATE area_commitment  SET xtime = {timestamp} WHERE mgnr IN ({mgnrs});
                    UPDATE delivery         SET xtime = {timestamp} WHERE (year, did) IN ({dids});
                    UPDATE delivery_part    SET xtime = {timestamp} WHERE (year, did) IN ({dids});
                    UPDATE client_parameter SET value = '1' WHERE param = 'ENABLE_TIME_TRIGGERS';
                    COMMIT;
                    """);
            }
            App.HintContextChange();
        }

        public static async Task<bool> ChangesAvailable(AppDbContext ctx, string url, string username, string password) {
            return await ctx.Members.AnyAsync(ChangedMembers) || await ctx.Deliveries.AnyAsync(ChangedDeliveries) || (Utils.HasInternetConnectivity() && (await GetFilesToImport(url, username, password)).Count > 0);
        }
    }
}
