using Elwig.Helpers;
using Elwig.Models.Entities;
using Elwig.ViewModels;
using LinqKit;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Elwig.Services {
    public static class DeliveryScheduleService {

        public static void InitInputs(this DeliveryScheduleAdminViewModel vm) {
            if (vm.BranchSource.Count() == 1)
                vm.Branch = vm.BranchSource.First();
            vm.AncmtFromTimeString = "00:00";
            vm.AncmtToTimeString = "23:59";
        }

        public static void ClearInputs(this DeliveryScheduleAdminViewModel vm) {
        }

        public static void FillInputs(this DeliveryScheduleAdminViewModel vm, DeliverySchedule s) {
            vm.Date = s.Date;
            vm.Branch = (Branch?)ControlUtils.GetItemFromSourceWithPk(vm.BranchSource, s.ZwstId);
            vm.Description = s.Description;
            vm.MaxWeight = s.MaxWeight;
            vm.IsCancelled = s.IsCancelled;
            vm.MainVarieties.Clear();
            foreach (var v in s.Varieties.Where(v => v.Priority == 1)) {
                vm.MainVarieties.Add((WineVar)ControlUtils.GetItemFromSourceWithPk(vm.MainVarietiesSource, v.SortId)!);
            }
            vm.OtherVarieties.Clear();
            foreach (var v in s.Varieties.Where(v => v.Priority != 1)) {
                vm.OtherVarieties.Add((WineVar)ControlUtils.GetItemFromSourceWithPk(vm.OtherVarietiesSource, v.SortId)!);
            }
            vm.Attribute = ControlUtils.GetItemFromSourceWithPk(vm.AttributeSource, s.AttrId) as WineAttr;
            vm.Cultivation = ControlUtils.GetItemFromSourceWithPk(vm.CultivationSource, s.CultId) as WineCult;
            vm.AncmtFrom = s.AncmtFrom;
            vm.AncmtTo = s.AncmtTo?.AddSeconds(-1);
        }

        public static async Task<(List<string>, IQueryable<DeliverySchedule>, List<string>)> GetFilters(this DeliveryScheduleAdminViewModel vm, AppDbContext ctx) {
            List<string> filterNames = [];
            IQueryable<DeliverySchedule> deliveryScheduleQuery = ctx.DeliverySchedules;
            if (vm.FilterSeason != null) {
                deliveryScheduleQuery = deliveryScheduleQuery.Where(s => s.Year == vm.FilterSeason);
                filterNames.Add($"{vm.FilterSeason}");
            }
            if (vm.FilterOnlyUpcoming) {
                deliveryScheduleQuery = deliveryScheduleQuery.Where(s => s.DateString.CompareTo(Utils.Today.ToString("yyyy-MM-dd")) >= 0);
                filterNames.Add($"ab {Utils.Today:dd.MM.yyyy}");
            }

            var filterVar = new List<string>();
            var filterNotVar = new List<string>();
            var filterZwst = new List<string>();
            var filterDate = new List<(string?, string?)>();

            var filter = vm.TextFilter;
            if (filter.Count > 0) {
                var var = await ctx.WineVarieties.ToDictionaryAsync(v => v.SortId, v => v);
                var zwst = await ctx.Branches.ToDictionaryAsync(b => b.Name.ToLower().Split(" ")[0], b => b);

                for (int i = 0; i < filter.Count; i++) {
                    var e = filter[i];
                    if (e.ToLower() is "r" or "rot") {
                        filterVar.AddRange(var.Values.Where(v => v.IsRed).Select(v => v.SortId));
                        filter.RemoveAt(i--);
                        filterNames.Add("Rotweinsorten");
                    } else if (e.ToLower() is "w" or "weiß" or "weiss") {
                        filterVar.AddRange(var.Values.Where(v => v.IsWhite).Select(v => v.SortId));
                        filter.RemoveAt(i--);
                        filterNames.Add("Weißweinsorten");
                    } else if (e.Length == 2 && var.ContainsKey(e.ToUpper())) {
                        filterVar.Add(e.ToUpper());
                        filter.RemoveAt(i--);
                        filterNames.Add(var[e.ToUpper()].Name);
                    } else if (e.Length == 3 && e[0] == '!' && var.ContainsKey(e[1..].ToUpper())) {
                        filterNotVar.Add(e[1..].ToUpper());
                        filter.RemoveAt(i--);
                        filterNames.Add("außer " + var[e[1..].ToUpper()].Name);
                    } else if (zwst.ContainsKey(e.ToLower())) {
                        var b = zwst[e.ToLower()];
                        filterZwst.Add(b.ZwstId);
                        filter.RemoveAt(i--);
                        filterNames.Add($"Zweigstelle {b.Name}");
                    } else if (DateOnly.TryParse(e, out var date)) {
                        var s = date.ToString("yyyy-MM-dd");
                        filterDate.Add((s, s));
                        filter.RemoveAt(i--);
                        if (filterNames.Contains($"{vm.FilterSeason}") && vm.FilterSeason == date.Year)
                            filterNames.Remove($"{vm.FilterSeason}");
                        filterNames.Add(date.ToString("dd.MM.yyyy"));
                    } else if (Utils.DateFromToRegex.IsMatch(e)) {
                        var parts = e.Split("-");
                        if (parts.Length == 1) {
                            // single date
                            var dParts = parts[0].Split('.');
                            var s = $"{dParts[2]}-{dParts[1].PadLeft(2, '0')}-{dParts[0].PadLeft(2, '0')}";
                            filterDate.Add((s, s));
                            filter.RemoveAt(i--);
                            var n = string.Join('.', s.Split('-').Reverse());
                            if (dParts[2] == "") {
                                filterNames.Remove($"{vm.FilterSeason}");
                                filterNames.Add(n + $"{vm.FilterSeason}");
                            } else {
                                if ($"{vm.FilterSeason}" == dParts[2])
                                    filterNames.Remove($"{vm.FilterSeason}");
                                filterNames.Add(n);
                            }
                        } else if (parts.Length == 2) {
                            // from/to date
                            var d1Parts = parts[0].Split('.');
                            var d2Parts = parts[1].Split('.');
                            var s1 = d1Parts.Length < 2 ? null : $"{d1Parts.ElementAtOrDefault(2)}-{d1Parts[1].PadLeft(2, '0')}-{d1Parts[0].PadLeft(2, '0')}";
                            var s2 = d2Parts.Length < 2 ? null : $"{d2Parts.ElementAtOrDefault(2)}-{d2Parts[1].PadLeft(2, '0')}-{d2Parts[0].PadLeft(2, '0')}";
                            filterDate.Add((s1, s2));
                            filter.RemoveAt(i--);
                            var n1 = s1 == null ? null : string.Join('.', s1.Split('-').Reverse());
                            var n2 = s2 == null ? null : string.Join('.', s2.Split('-').Reverse());
                            if (n1 != null && n2 != null) {
                                filterNames.Add($"{n1}–{n2}");
                            } else if (n1 != null) {
                                filterNames.Add($"ab dem {n1}");
                            } else if (n2 != null) {
                                filterNames.Add($"bis zum {n2}");
                            }
                        }
                    } else if (e.Length > 2 && e.StartsWith('"') && e.EndsWith('"')) {
                        filter[i] = e[1..^1];
                    } else if (e.Length <= 2) {
                        filter.RemoveAt(i--);
                    }
                }

                if (filterDate.Count > 0) {
                    var pr = PredicateBuilder.New<DeliverySchedule>(false);
                    foreach (var (d1, d2) in filterDate)
                        pr.Or(p => (d1 == null || d1.CompareTo(p.DateString.Substring(10 - d1.Length)) <= 0) && (d2 == null || d2.CompareTo(p.DateString.Substring(10 - d2.Length)) >= 0));
                    deliveryScheduleQuery = deliveryScheduleQuery.Where(pr);
                }
                if (filterVar.Count > 0) deliveryScheduleQuery = deliveryScheduleQuery.Where(s => s.Varieties.Any(v => filterVar.Contains(v.SortId)));
                if (filterNotVar.Count > 0) deliveryScheduleQuery = deliveryScheduleQuery.Where(s => s.Varieties.All(v => !filterNotVar.Contains(v.SortId)));
                if (filterZwst.Count > 0) deliveryScheduleQuery = deliveryScheduleQuery.Where(s => filterZwst.Contains(s.Branch.ZwstId));
            }

            return (filterNames, deliveryScheduleQuery, filter);
        }

        public static async Task UpdateDeliverySchedule(this DeliveryScheduleAdminViewModel vm, int? oldYear, int? oldDsNr) {
            int year = vm.Date!.Value.Year;

            await Task.Run(async () => {
                using var ctx = new AppDbContext();
                var s = new DeliverySchedule {
                    Year = oldYear ?? year,
                    DsNr = oldDsNr ?? await ctx.NextDsNr(year),
                    DateString = $"{vm.Date:yyyy-MM-dd}",
                    ZwstId = vm.Branch!.ZwstId,
                    Description = vm.Description,
                    AttrId = vm.Attribute?.AttrId,
                    CultId = vm.Cultivation?.CultId,
                    MaxWeight = vm.MaxWeight,
                    IsCancelled = vm.IsCancelled,
                    AncmtFrom = vm.AncmtFrom,
                    AncmtTo = vm.AncmtTo?.AddMinutes(1),
                };

                if (oldDsNr != null) {
                    ctx.Update(s);
                } else {
                    ctx.Add(s);
                }

                ctx.UpdateDeliveryScheduleWineVarieties(s, (await ctx.DeliveryScheduleWineVarieties
                    .Where(v => v.Year == s.Year && v.DsNr == s.DsNr)
                    .Select(v => new { v.Variety, v.Priority })
                    .ToListAsync())
                    .Select(v => (v.Variety, v.Priority))
                    .ToList(), vm.MainVarieties.Select(v => (v, 1)).Union(vm.OtherVarieties.Select(v => (v, 2))).ToList());

                await ctx.SaveChangesAsync();
            });
        }
    }
}
