using System.IO.Compression;
using System.IO;
using System.Threading.Tasks;
using Elwig.Models.Entities;
using System.Collections.Generic;
using System;
using System.Text.Json.Nodes;
using System.Linq;
using System.Windows;
using Microsoft.EntityFrameworkCore;

namespace Elwig.Helpers.Export {
    public static class ElwigData {

        public static readonly string ImportedTxt = Path.Combine(App.DataPath, "imported.txt");

        public static async Task<string[]> GetImportedFiles() {
            try {
                return await File.ReadAllLinesAsync(ImportedTxt, Utils.UTF8);
            } catch {
                return [];
            }
        }

        public static async Task AddImportedFiles(IEnumerable<string> filenames) {
            await File.AppendAllLinesAsync(ImportedTxt, filenames, Utils.UTF8);
        }

        public static Task Import(string filename, bool interactive) => Import([filename], interactive);

        public static async Task Import(IEnumerable<string> filenames, bool interactive) {
            try {
                using var ctx = new AppDbContext();
                var currentDids = await ctx.Deliveries
                    .GroupBy(d => d.Year)
                    .ToDictionaryAsync(g => g.Key, g => g.Max(d => d.DId));

                var deliveries = new List<Delivery>();
                var deliveryParts = new List<DeliveryPart>();
                var modifiers = new List<DeliveryPartModifier>();

                var metaData = new List<(string Name, int DeliveryNum, string Filters)>();

                foreach (var filename in filenames) {
                    using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);

                    var version = zip.GetEntry("version");
                    using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) {
                        if (await reader.ReadToEndAsync() != "elwig:1")
                            throw new FileFormatException("Ungültige Export-Datei");
                    }

                    var metaJson = zip.GetEntry("meta.json");
                    var meta = await JsonNode.ParseAsync(metaJson!.Open());
                    var deliveryCount = meta!["deliveries"]?["count"]!.AsValue().GetValue<int>();
                    var deliveryFilters = meta!["deliveries"]?["filters"]!.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
                    if (deliveryCount != null && deliveryFilters != null)
                        metaData.Add((Path.GetFileName(filename), (int)deliveryCount, string.Join(" / ", deliveryFilters)));

                    var membersJson = zip.GetEntry("members.json");
                    if (membersJson != null) {
                        using var reader = new StreamReader(membersJson.Open(), Utils.UTF8);
                        string? line;
                        while ((line = await reader.ReadLineAsync()) != null) {
                            var obj = JsonNode.Parse(line)!.AsObject();
                            // TODO import members.json
                        }
                    }

                    var areaComsJson = zip.GetEntry("area_commitments.json");
                    if (areaComsJson != null) {
                        using var reader = new StreamReader(areaComsJson.Open(), Utils.UTF8);
                        string? line;
                        while ((line = await reader.ReadLineAsync()) != null) {
                            var obj = JsonNode.Parse(line)!.AsObject();
                            // TODO import area_commitments.json
                        }
                    }

                    var deliveriesJson = zip.GetEntry("deliveries.json");
                    if (deliveriesJson != null) {
                        using var reader = new StreamReader(deliveriesJson.Open(), Utils.UTF8);
                        string? line;
                        while ((line = await reader.ReadLineAsync()) != null) {
                            var obj = JsonNode.Parse(line)!.AsObject();
                            var (d, parts, mods) = JsonToDelivery(obj, currentDids);
                            deliveries.Add(d);
                            deliveryParts.AddRange(parts);
                            modifiers.AddRange(mods);
                        }
                    }
                }

                var lsnrs = deliveries.Select(d => d.LsNr).ToList();
                var duplicateLsNrs = await ctx.Deliveries
                    .Where(d => lsnrs.Contains(d.LsNr))
                    .Select(d => d.LsNr)
                    .ToListAsync();
                var duplicateDIds = deliveries
                    .Where(d => duplicateLsNrs.Contains(d.LsNr))
                    .Select(d => (d.Year, d.DId))
                    .ToList();
                bool overwriteDelivieries = false;
                if (duplicateLsNrs.Count > 0) {
                    var res = MessageBox.Show($"Sollen {duplicateLsNrs.Count} Lieferungen überschreiben werden?", "Lieferungen überschreiben",
                        MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No);
                    overwriteDelivieries = res == MessageBoxResult.Yes;
                }
                if (overwriteDelivieries) {
                    ctx.RemoveRange(ctx.Deliveries.Where(d => duplicateLsNrs.Contains(d.LsNr)));
                    ctx.AddRange(deliveries);
                    ctx.AddRange(deliveryParts);
                    ctx.AddRange(modifiers);
                } else {
                    ctx.AddRange(deliveries.Where(d => !duplicateDIds.Contains((d.Year, d.DId))));
                    ctx.AddRange(deliveryParts.Where(p => !duplicateDIds.Contains((p.Year, p.DId))));
                    ctx.AddRange(modifiers.Where(m => !duplicateDIds.Contains((m.Year, m.DId))));
                }
                await ctx.SaveChangesAsync();
                await AddImportedFiles(filenames.Select(f => Path.GetFileName(f)));
                await App.HintContextChange();

                MessageBox.Show(
                    $"Das importieren der Daten war erfolgreich!\n" +
                    $"Folgendes wurde importiert:\n" +
                    $"  Lieferungen: {deliveries.Count}\n" +
                    string.Join("\n", metaData.Select(d => $"    {d.Name} ({d.DeliveryNum})\n      {d.Filters}")) +
                    "\n", "Importieren erfolgreich",
                    MessageBoxButton.OK, MessageBoxImage.Information);
            } catch (Exception exc) {
                MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        public static async Task ExportDeliveries(string filename, IEnumerable<Delivery> deliveries, IEnumerable<string> filters) {
            File.Delete(filename);
            using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);

            var version = zip.CreateEntry("version", CompressionLevel.NoCompression);
            using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) {
                await writer.WriteAsync("elwig:1");
            }

            var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression);
            using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) {
                await writer.WriteAsync(
                    $"{{\"timestamp\": \"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}\", " +
                    $"\"zwstid\": \"{App.ZwstId}\", \"device\": \"{Environment.MachineName}\", " +
                    $"\"deliveries\": {{" +
                    $"\"count\": {deliveries.Count()}, " +
                    $"\"parts\": {deliveries.Sum(d => d.Parts.Count)}, " +
                    $"\"filters\": [{string.Join(", ", filters.Select(f => '"' + f + '"'))}]" +
                    $"}}}}");
            }

            var json = zip.CreateEntry("deliveries.json");
            using (var writer = new StreamWriter(json.Open(), Utils.UTF8)) {
                foreach (var d in deliveries) {
                    await writer.WriteLineAsync(DeliveryToJson(d).ToJsonString());
                }
            }
        }

        public static JsonObject DeliveryToJson(Delivery d) {
            return new JsonObject {
                ["lsnr"] = d.LsNr,
                ["year"] = d.Year,
                ["date"] = $"{d.Date:yyyy-MM-dd}",
                ["zwstid"] = d.ZwstId,
                ["lnr"] = d.LNr,
                ["time"] = d.Time != null ? $"{d.Time:HH:mm:ss}" : null,
                ["mgnr"] = d.MgNr,
                ["parts"] = new JsonArray(d.Parts.OrderBy(p => p.DPNr).Select(p => {
                    var obj = new JsonObject {
                        ["dpnr"] = p.DPNr,
                        ["sortid"] = p.SortId,
                        ["attrid"] = p.AttrId,
                        ["cultid"] = p.CultId,
                        ["weight"] = p.Weight,
                        ["kmw"] = p.Kmw,
                        ["qualid"] = p.QualId,
                        ["hkid"] = p.HkId,
                        ["kgnr"] = p.KgNr,
                        ["rdnr"] = p.RdNr,
                        ["net_weight"] = p.IsNetWeight,
                        ["manual_weighing"] = p.IsManualWeighing,
                        ["modids"] = new JsonArray(p.Modifiers.Select(m => (JsonNode)m.ModId).ToArray()),
                        ["comment"] = p.Comment,
                    };
                    if (p.IsSplCheck) obj["spl_check"] = p.IsSplCheck;
                    if (p.IsHandPicked != null) obj["hand_picked"] = p.IsHandPicked;
                    if (p.IsLesewagen != null) obj["lesewagen"] = p.IsLesewagen;
                    if (p.IsGebunden != null) obj["gebunden"] = p.IsGebunden;
                    if (p.Temperature != null) obj["temperature"] = p.Temperature;
                    if (p.Acid != null) obj["acid"] = p.Acid;
                    if (p.ScaleId != null) obj["scale_id"] = p.ScaleId;
                    if (p.WeighingId != null) obj["weighing_id"] = p.WeighingId;
                    if (p.WeighingReason != null) obj["weighing_reason"] = p.WeighingReason;
                    return obj;
                }).ToArray()),
                ["comment"] = d.Comment,
            };
        }

        public static (Delivery, List<DeliveryPart>, List<DeliveryPartModifier>) JsonToDelivery(JsonNode json, Dictionary<int, int> currentDids) {
            var year = json["year"]!.AsValue().GetValue<int>();
            var did = ++currentDids[year];
            return (new Delivery {
                Year = year,
                DId = did,
                DateString = json["date"]!.AsValue().GetValue<string>(),
                TimeString = json["time"]?.AsValue().GetValue<string>(),
                ZwstId = json["zwstid"]!.AsValue().GetValue<string>(),
                LNr = json["lnr"]!.AsValue().GetValue<int>(),
                LsNr = json["lsnr"]!.AsValue().GetValue<string>(),
                MgNr = json["mgnr"]!.AsValue().GetValue<int>(),
                Comment = json["comment"]?.AsValue().GetValue<string>(),
            }, json["parts"]!.AsArray().Select(p => p!.AsObject()).Select(p => new DeliveryPart {
                Year = year,
                DId = did,
                DPNr = p["dpnr"]!.AsValue().GetValue<int>(),
                SortId = p["sortid"]!.AsValue().GetValue<string>(),
                AttrId = p["attrid"]?.AsValue().GetValue<string>(),
                CultId = p["cultid"]?.AsValue().GetValue<string>(),
                Weight = p["weight"]!.AsValue().GetValue<int>(),
                Kmw = p["kmw"]!.AsValue().GetValue<double>(),
                QualId = p["qualid"]!.AsValue().GetValue<string>(),
                HkId = p["hkid"]!.AsValue().GetValue<string>(),
                KgNr = p["kgnr"]?.AsValue().GetValue<int>(),
                RdNr = p["rdnr"]?.AsValue().GetValue<int>(),
                IsNetWeight = p["net_weight"]!.AsValue().GetValue<bool>(),
                IsManualWeighing = p["manual_weighing"]!.AsValue().GetValue<bool>(),
                Comment = p["comment"]?.AsValue().GetValue<string>(),
                IsSplCheck = p["spl_check"]?.AsValue().GetValue<bool>() ?? false,
                IsHandPicked = p["hand_picked"]?.AsValue().GetValue<bool>(),
                IsLesewagen = p["lesewagen"]?.AsValue().GetValue<bool>(),
                IsGebunden = p["gebunden"]?.AsValue().GetValue<bool>(),
                Temperature = p["temperature"]?.AsValue().GetValue<double>(),
                Acid = p["acid"]?.AsValue().GetValue<double>(),
                ScaleId = p["scale_id"]?.AsValue().GetValue<string>(),
                WeighingId = p["weighing_id"]?.AsValue().GetValue<string>(),
                WeighingReason = p["weighing_reason"]?.AsValue().GetValue<string>(),
            }).ToList(), json["parts"]!.AsArray().SelectMany(p => p!["modids"]!.AsArray().Select(m => new DeliveryPartModifier {
                Year = year,
                DId = did,
                DPNr = p["dpnr"]!.AsValue().GetValue<int>(),
                ModId = m!.AsValue().GetValue<string>(),
            })).ToList());
        }
    }
}