using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder;
using System.Linq;
using System.Threading.Tasks;

namespace Elwig.Models.Dtos {
    public class WineQualityStatisticsData {

        public record struct QualityRow(string? Variety, string? Attribute, string? Cultivation, string? Type, string QualId, double AvgKmw, double Grad, int Num, int Weight);
        public record struct QualitySection(string Name, string? Type, Dictionary<string, (double Grad, double AvgKmw, int Num, int Weight)[]> Data);

        public bool UseOe = true;
        public QualitySection[] Sections;

        public WineQualityStatisticsData(QualitySection[] sections) {
            Sections = sections;
        }

        private static QualitySection[] GetSections(IEnumerable<QualityRow> rows) {
            var data = new List<QualitySection>();
            var currentQual = new Dictionary<double, (double AvgKmw, int Num, int Weight)>();
            var current = new Dictionary<string, (double, double, int, int)[]>();
            string? lastSection = null;
            string? lastType = null;
            string? lastQual = null;
            foreach (var row in rows) {
                var sec = $"{row.Variety ?? (row.Type == "R" ? "Rotweinsorten" : row.Type == "W" ? "Weißweinsorten" : "Gesamt")}" +
                    $"{(row.Attribute != null ? " / " : "")}{row.Attribute}" +
                    $"{(row.Cultivation != null ? " / " : "")}{row.Cultivation}";
                if (lastQual != null && lastQual != row.QualId) {
                    current[lastQual] = currentQual.Select(kv => (kv.Key, kv.Value.AvgKmw, kv.Value.Num, kv.Value.Weight)).ToArray();
                    currentQual.Clear();
                }
                if (lastSection != null && lastSection != sec) {
                    if (!current.ContainsKey(lastQual!)) {
                        current[lastQual!] = currentQual.Select(kv => (kv.Key, kv.Value.AvgKmw, kv.Value.Num, kv.Value.Weight)).ToArray();
                        currentQual.Clear();
                    }
                    data.Add(new(lastSection, lastType, current));
                    current = [];
                    currentQual.Clear();
                }
                currentQual[row.Grad] = (row.AvgKmw, row.Num, row.Weight);
                lastSection = sec;
                lastType = row.Type;
                lastQual = row.QualId;
            }
            if (lastQual != null) {
                current[lastQual] = currentQual.Select(kv => (kv.Key, kv.Value.AvgKmw, kv.Value.Num, kv.Value.Weight)).ToArray();
                currentQual.Clear();
            }
            if (lastSection != null) {
                data.Add(new(lastSection, lastType, current));
                current = [];
                currentQual.Clear();
            }
            return [.. data];
        }

        public static async Task<WineQualityStatisticsData> FromQuery(IQueryable<DeliveryPart> query, int mode = 0) {
            var rows = (await query
                .GroupBy(p => new {
                    p.Variety.Type,
                    Variety = p.Variety.Name,
                    Attribute = p.Attribute!.Name,
                    Cultivation = p.Cultivation!.Name,
                    p.QualId,
                    Grad = mode == 0 ? Math.Round(p.Kmw * (4.54 + 0.022 * p.Kmw), 0) :
                           mode == 1 ? Math.Floor(p.Kmw) :
                           mode == 2 ? Math.Floor(p.Kmw * 2) / 2 :
                           mode == 3 ? Math.Floor(p.Kmw * 5) / 5 :
                                       Math.Round(p.Kmw, 1),
                }, (k, g) => new {
                    Key = k,
                    Num = g.Count(),
                    Weight = g.Sum(p => p.Weight),
                    AvgKmw = g.Sum(p => p.Weight * p.Kmw) / g.Sum(p => p.Weight),
                })
                .OrderBy(g => g.Key.Variety)
                .ThenBy(g => g.Key.Attribute)
                .ThenBy(g => g.Key.Cultivation)
                .ThenBy(g => g.Key.QualId)
                .ThenBy(g => g.Key.Grad)
                .ToListAsync())
                .Select(r => new QualityRow(r.Key.Variety, r.Key.Attribute, r.Key.Cultivation, r.Key.Type, r.Key.QualId, r.AvgKmw, r.Key.Grad, r.Num, r.Weight))
                .ToList();

            var data = GetSections(rows);
            if (data.Length <= 1)
                return new(data);

            var typeRows = rows
                .GroupBy(s => new { s.Type, s.QualId, s.Grad }, (k, g) => new QualityRow(
                        null, null, null,
                        k.Type, k.QualId,
                        g.Sum(p => p.Weight * p.AvgKmw) / g.Sum(p => p.Weight),
                        k.Grad,
                        g.Sum(p => p.Num),
                        g.Sum(p => p.Weight)
                    ))
                .OrderBy(g => g.Type)
                .ThenBy(g => g.QualId)
                .ThenBy(g => g.Grad)
                .ToList();
            var typeData = GetSections(typeRows);
            if (typeData.Length <= 1)
                return new([.. typeData, .. data]);

            var totalRows = rows
                .GroupBy(s => new { s.QualId, s.Grad }, (k, g) => new QualityRow(
                        null, null, null, null,
                        k.QualId,
                        g.Sum(p => p.Weight * p.AvgKmw) / g.Sum(p => p.Weight),
                        k.Grad,
                        g.Sum(p => p.Num),
                        g.Sum(p => p.Weight)
                    ))
                .OrderBy(g => g.QualId)
                .ThenBy(g => g.Grad)
                .ToList();
            var totalData = GetSections(totalRows);
            return new([.. totalData, .. typeData, .. data]) { UseOe = mode == 0 };
        }
    }
}
