using Elwig.Helpers;
using Elwig.Models;
using Elwig.Models.Entities;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace Elwig.Documents {
    public abstract class BusinessDocument : Document {

        public Member Member;
        public string? Location;
        public bool IncludeSender = false;
        public bool UseBillingAddress = false;
        public bool ShowDateAndLocation = false;
        public string Aside;

        public BusinessDocument(string title, Member m, bool includeSender = false) : base(title) {
            Member = m;
            Location = App.BranchLocation;
            IncludeSender = includeSender;
            var uid = (m.UstIdNr ?? "-") + (m.IsBuchführend ? "" : " <i>(pauschaliert)</i>");
            Aside = $"<table><colgroup><col span='1' style='width: 22.5mm;'/><col span='1' style='width: 42.5mm;'/></colgroup>" +
                $"<thead><tr><th colspan='2'>Mitglied</th></tr></thead><tbody>" +
                $"<tr><th>Mitglieds-Nr.</th><td>{m.MgNr}</td></tr>" +
                $"<tr><th>Betriebs-Nr.</th><td>{m.LfbisNr}</td></tr>" +
                $"<tr><th>UID</th><td>{uid}</td></tr>" +
                $"</tbody></table>";
        }

        public string Address {
            get {
                IAddress addr = (Member.BillingAddress != null && UseBillingAddress) ? Member.BillingAddress : Member;
                var plz = addr.PostalDest.AtPlz;
                return (addr is BillingAddr ? $"{addr.Name}\n" : "") + $"{Member.AdministrativeName}\n{addr.Address}\n{plz?.Plz} {plz?.Ort.Name.Split(",")[0]}\n{addr.PostalDest.Country.Name}";
            }
        }

        private static string GetColGroup(IEnumerable<double> cols) {
            return "<colgroup>\n" + string.Join("\n", cols.Select(g => $"<col style=\"width: {g.ToString(CultureInfo.InvariantCulture)}mm;\"/>")) + "\n</colgroup>\n";
        }

        public static string PrintSortenaufteilung(List<MemberStat> stats) {
            List<string> discrs = [""];
            List<string> names = ["ohne Attr./Bewirt."];
            List<string> bucketAttrs = [
                .. stats
                .Select(s => s.Discr)
                .Distinct()
                .Where(s => s.Length > 0)
                .Order()
            ];
            names.AddRange(bucketAttrs);
            names.Add("Gesamt");
            discrs.AddRange(bucketAttrs);

            List<double> cols = [40];
            cols.AddRange(names.Select(_ => 125.0 / names.Count));
            string tbl = GetColGroup(cols);
            tbl += "<thead><tr>" +
                $"<th><b>Sortenaufteilung</b> [kg]</th>" +
                string.Join("", names.Select(c => $"<th>{c}</th>")) +
                "</tr></thead>";

            tbl += string.Join("\n", stats
                .GroupBy(b => b.Variety)
                .OrderBy(b => b.Key)
                .Select(g => {
                    var dict = g.ToDictionary(a => a.Discr, a => a.Weight);
                    var vals = discrs.Select(a => dict.GetValueOrDefault(a, 0)).ToList();
                    return $"<tr><th>{g.Key}</th>" + string.Join("", vals.Select(v => "<td class=\"number\">" + (v == 0 ? "-" : $"{v:N0}") + "</td>")) +
                        $"<td class=\"number\">{dict.Values.Sum():N0}</td></tr>";
                })
            );
            var totalDict = stats.GroupBy(s => s.Discr).ToDictionary(g => g.Key, g => g.Sum(a => a.Weight));
            var totals = discrs.Select(a => totalDict.TryGetValue(a, out int value) ? value : 0);
            tbl += "<tr class=\"sum bold\"><td></td>" + string.Join("", totals.Select(v => $"<td class=\"number\">{v:N0}</td>")) +
                $"<td class=\"number\">{totalDict.Values.Sum():N0}</td></tr>";

            return "<table class=\"sortenaufteilung small number cohere\">" + tbl + "</table>";
        }

        private static string FormatRow(
            int obligation, int right, int delivery, int? payment = null, int? area = null,
            bool isGa = false, bool showPayment = false, bool showArea = false
        ) {
            payment ??= delivery;
            var baseline = showPayment ? payment : delivery;

            if (showArea) {
                return $"<td>{(area == null ? "" : $"{area:N0}")}</td>" +
                    $"<td>{obligation:N0}</td>" +
                    $"<td>{right:N0}</td>";
            }

            return $"<td>{(obligation == 0 ? "-" : $"{obligation:N0}")}</td>" +
                $"<td>{(right == 0 ? "-" : $"{right:N0}")}</td>" +
                $"<td>{(baseline < obligation ? $"<b>{obligation - baseline:N0}</b>" : "-")}</td>" +
                $"<td>{(baseline >= obligation && delivery <= right ? $"{right - delivery:N0}" : "-")}</td>" +
                $"<td>{(obligation == 0 && right == 0 ? "-" : (delivery > right ? ((isGa ? "<b>" : "") + $"{delivery - right:N0}" + (isGa ? "</b>" : "")) : "-"))}</td>" +
                (showPayment ? $"<td>{(isGa ? "" : obligation == 0 && right == 0 ? "-" : $"{payment:N0}")}</td>" : "") +
                $"<td>{delivery:N0}</td>";
        }

        private static string FormatRow(MemberBucket bucket, bool isGa = false, bool showPayment = false, bool showArea = false) {
            return FormatRow(bucket.Obligation, bucket.Right, bucket.Delivery, bucket.Payment, bucket.Area, isGa, showPayment, showArea);
        }

        public string PrintBucketTable(
            Season season, Dictionary<string, MemberBucket> buckets,
            bool includeDelivery = true, bool includePayment = false,
            bool isTiny = false, IEnumerable<string>? filter = null
        ) {
            includePayment = includePayment && includeDelivery;
            string tbl = GetColGroup(!includeDelivery ? [105, 20, 20, 20] : includePayment ? [45, 17, 17, 17, 19, 16, 17, 17] : [45, 20, 20, 20, 20, 20, 20]);
            tbl += $"""
                <thead>
                    <tr>
                        <th{(!includeDelivery ? " rowspan=\"2\"" : "")}>
                            <b>{(includeDelivery ? "Lese " + season.Year : "Zusammengefasste Flächenbindungen")}</b>
                            per {Date:dd.MM.yyyy} {(includeDelivery ? "[kg]" : "")}
                        </th>
                        {(!includeDelivery ? "<th>Fläche</th>" : "")}
                        <th>Lieferpflicht</th>
                        <th>Lieferrecht</th>
                        {(includeDelivery ? "<th>Unterliefert</th>" : "")}
                        {(includeDelivery ? "<th>Noch lieferbar</th>" : "")}
                        {(includeDelivery ? "<th>Überliefert</th>" : "")}
                        {(includePayment ? "<th>Zugeteilt</th>" : "")}
                        {(includeDelivery ? "<th>Geliefert</th>" : "")}
                    </tr>
                    {(!includeDelivery ? "<tr><th class=\"unit\">[m²]</th><th class=\"unit\">[kg]</th><th class=\"unit\">[kg]</th></tr>" : "")}
                </thead>
                """;

            var mBuckets = buckets
                .Where(b => ((!includeDelivery && b.Value.Area > 0) ||
                             (includeDelivery && (b.Value.Right > 0 || b.Value.Obligation > 0 || b.Value.Delivery > 0))) &&
                            (filter == null || filter.Contains(b.Key[..2])))
                .ToList();
            var fbVars = mBuckets
                .Where(b => b.Value.Right > 0 || b.Value.Obligation > 0)
                .Select(b => b.Key.Replace("_", ""))
                .Order()
                .ToArray();
            var fbs = mBuckets
                .Where(b => fbVars.Contains(b.Key) && b.Key.Length == 2)
                .OrderBy(b => b.Value.Name);
            var vtr = mBuckets
                .Where(b => fbVars.Contains(b.Key) && b.Key.Length > 2)
                .OrderBy(b => b.Value.Name);
            var rem = mBuckets
                .Where(b => !fbVars.Contains(b.Key))
                .OrderBy(b => b.Value.Name);

            tbl += "\n<tbody>\n";
            tbl += $"<tr><th>Gesamtlieferung lt. gez. GA</th>{FormatRow(
                    Member.BusinessShares * season.MinKgPerBusinessShare,
                    Member.BusinessShares * season.MaxKgPerBusinessShare,
                    season.Deliveries.Where(d => d.MgNr == Member.MgNr).Sum(d => d.Weight),
                    isGa: true, showPayment: includePayment, showArea: !includeDelivery
                )}</tr>";
            if (fbs.Any()) {
                tbl += $"<tr class=\"subheading{(filter == null ? " border" : "")}\"><th colspan=\"{(includePayment ? 8 : 7)}\">" +
                    $"Flächenbindungen{(vtr.Any() ? " (inkl. Verträge)" : "")}:</th></tr>";
                foreach (var (id, b) in fbs) {
                    tbl += $"<tr><th>{b.Name}</th>{FormatRow(b, showPayment: includePayment, showArea: !includeDelivery)}</tr>";
                }
            }
            if (vtr.Any()) {
                tbl += $"<tr class=\"subheading{(filter == null ? " border" : "")}\"><th colspan=\"{(includePayment ? 8 : 7)}\">" +
                    "Verträge:</th></tr>";
                foreach (var (id, b) in vtr) {
                    tbl += $"<tr><th>{b.Name}</th>{FormatRow(b, showPayment: includePayment, showArea: !includeDelivery)}</tr>";
                }
            }
            tbl += "\n</tbody>\n";

            return $"<table class=\"buckets {(isTiny ? "tiny" : "small")} number cohere\">\n" + tbl + "\n</table>";
        }
    }
}