using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder;
using System.Linq;
using System.Threading.Tasks;

namespace Elwig.Models.Dtos {
    public class CreditNoteData : DataTable<CreditNoteRow> {

        private static readonly (string, string, string?, int)[] FieldNames = [
            ("MgNr", "MgNr.", null, 12),
            ("Name", "Name", null, 40),
            ("GivenName", "Vorname", null, 40),
            ("Address", "Adresse", null, 60),
            ("Plz", "PLZ", null, 10),
            ("Locality", "Ort", null, 60),
            ("Iban", "IBAN", null, 45),
            ("TgNr", "TG-Nr.", null, 20),
            ("Sum", "Zwischens.", "€", 20),
            ("Surcharge", "Zuschlag", "€", 20),
            ("Total", "Gesamt", "€", 20),
            ("ConsideredSum", "Berückstgt.", "€", 20),
            ("Net", "Netto", "€", 20),
            ("Vat1", "10% MwSt.", "€", 20),
            ("Vat2", "13% MwSt.", "€", 20),
            ("Gross", "Brutto", "€", 20),
            ("Penalties", "Pönalen FB", "€", 20),
            ("Penalty", "Unterl. GA", "€", 20),
            ("AutoBs", "GA Nachz.", "€", 20),
            ("Others", "Sonstige", "€", 20),
            ("Considered", "Berückstgt.", "€", 20),
            ("Amount", "Betrag", "€", 20),
        ];

        public CreditNoteData(IEnumerable<CreditNoteRow> rows, int year, string name) :
            base($"Buchungsliste", $"Buchungsliste {name} {year}", rows, FieldNames) {
        }

        public static async Task<CreditNoteData> ForPaymentVariant(AppDbContext ctx, int year, int avnr) {
            var variant = await ctx.PaymentVariants.FindAsync(year, avnr);
            var name = variant!.Name;
            var data = BillingData.FromJson(variant!.Data);
            var rows = (await FromDbSet(ctx.CreditNoteRows, year, avnr)).Select(r => new CreditNoteRow(r, data)).ToList();
            return new CreditNoteData(rows, year, name);
        }

        private static async Task<IEnumerable<CreditNoteRowSingle>> FromDbSet(DbSet<CreditNoteRowSingle> table, int year, int avnr) {
            return await table.FromSqlRaw($"""
                SELECT m.mgnr, m.family_name, m.given_name, p.plz, o.name AS ort, m.address, m.iban, c.tgnr, s.year, s.precision,
                       p.amount - p.net_amount AS surcharge,
                       c.net_amount, c.prev_net_amount, c.vat, c.vat_amount, c.gross_amount, c.modifiers, c.prev_modifiers, c.amount,
                       ROUND(COALESCE(u.total_penalty, 0) / POW(10, 4 - 2)) AS fb_penalty,
                       ROUND(COALESCE(b.total_penalty, 0) / POW(10, s.precision - 2)) AS bs_penalty,
                       ROUND(COALESCE(a.total_amount, 0) / POW(10, s.precision - 2)) AS auto_bs
                FROM credit c
                    LEFT JOIN member m ON m.mgnr = c.mgnr
                    LEFT JOIN payment_member p ON (p.year, p.avnr, p.mgnr) = (c.year, c.avnr, c.mgnr) 
                    LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest
                    LEFT JOIN AT_ort o ON o.okz = p.okz
                    LEFT JOIN season s ON s.year = c.year
                    LEFT JOIN v_penalty_area_commitments u ON (u.year, u.mgnr) = (s.year, m.mgnr)
                    LEFT JOIN v_penalty_business_shares b ON (b.year, b.mgnr) = (s.year, m.mgnr)
                    LEFT JOIN v_auto_business_shares a ON (a.year, a.mgnr) = (s.year, m.mgnr)
                WHERE c.year = {year} AND c.avnr = {avnr}
                ORDER BY m.mgnr
                """).ToListAsync();
        }
    }

    public class CreditNoteRow {
        public int MgNr;
        public string Name;
        public string GivenName;
        public string Address;
        public int Plz;
        public string Locality;
        public string? Iban;
        public string TgNr;
        public decimal Sum;
        public decimal? Surcharge;
        public decimal Total;
        public decimal? ConsideredSum;
        public decimal Net;
        public decimal? Vat1, Vat2;
        public decimal Gross;
        public decimal? Penalties;
        public decimal? Penalty;
        public decimal? AutoBs;
        public decimal? Others;
        public decimal? Considered;
        public decimal Amount;

        public CreditNoteRow(CreditNoteRowSingle row, BillingData data) {
            byte prec1 = 2, prec2 = row.Precision;
            MgNr = row.MgNr;
            Name = row.Name;
            GivenName = row.GivenName;
            Address = row.Address;
            Plz = row.Plz;
            Locality = row.Locality;
            Iban = row.Iban != null ? Utils.FormatIban(row.Iban) : null;
            TgNr = $"{row.Year}/{row.TgNr}";
            Total = Utils.DecFromDb(row.NetAmount, prec1);
            Surcharge = (row.Surcharge == null || row.Surcharge == 0) ? null : Utils.DecFromDb((long)row.Surcharge, prec2);
            Sum = Total - (Surcharge ?? 0);
            ConsideredSum = (row.PrevNetAmount == null ||row.PrevNetAmount == 0) ? null : -Utils.DecFromDb((long)row.PrevNetAmount, prec1);
            Net = Total + (ConsideredSum ?? 0);
            if (row.Vat == 0.10) {
                Vat1 = Utils.DecFromDb(row.VatAmount, prec1);
            } else if (row.Vat == 0.13) {
                Vat2 = Utils.DecFromDb(row.VatAmount, prec1);
            }
            decimal mod = (row.Modifiers == null) ? 0 : Utils.DecFromDb((long)row.Modifiers, prec1);
            if (data.ConsiderContractPenalties)
                Penalties = (row.FbPenalty == null || row.FbPenalty == 0) ? null : Utils.DecFromDb((long)row.FbPenalty, prec1);
            if (data.ConsiderTotalPenalty)
                Penalty = (row.BsPealty == null || row.BsPealty == 0) ? null : Utils.DecFromDb((long)row.BsPealty, prec1);
            if (data.ConsiderAutoBusinessShares)
                AutoBs = (row.AutoBs == null || row.AutoBs == 0) ? null : -Utils.DecFromDb((long)row.AutoBs, prec1);
            mod -= (Penalties ?? 0) + (Penalty ?? 0) + (AutoBs ?? 0);
            Others = (mod == 0) ? null : mod;
            Gross = Utils.DecFromDb(row.GrossAmount, prec1);
            Considered = (row.PrevModifiers == null || row.PrevModifiers == 0) ? null : -Utils.DecFromDb((long)row.PrevModifiers, prec1);
            Amount = Utils.DecFromDb(row.Amount, prec1);
        }
    }

    [Keyless]
    public class CreditNoteRowSingle {
        [Column("mgnr")]
        public int MgNr { get; set; }
        [Column("family_name")]
        public required string Name { get; set; }
        [Column("given_name")]
        public required string GivenName { get; set; }
        [Column("address")]
        public required string Address { get; set; }
        [Column("plz")]
        public int Plz { get; set; }
        [Column("ort")]
        public required string LocalityFull { get; set; }
        [NotMapped]
        public string Locality => LocalityFull.Split(",")[0];
        [Column("iban")]
        public string? Iban { get; set; }
        [Column("year")]
        public int Year { get; set; }
        [Column("precision")]
        public byte Precision { get; set; }
        [Column("tgnr")]
        public required string TgNr { get; set; }
        [Column("surcharge")]
        public long? Surcharge { get; set; }
        [Column("net_amount")]
        public long NetAmount { get; set; }
        [Column("prev_net_amount")]
        public long? PrevNetAmount { get; set; }
        [Column("vat")]
        public double Vat { get; set; }
        [Column("vat_amount")]
        public long VatAmount { get; set; }
        [Column("gross_amount")]
        public long GrossAmount { get; set; }
        [Column("modifiers")]
        public long? Modifiers { get; set; }
        [Column("prev_modifiers")]
        public long? PrevModifiers { get; set; }
        [Column("amount")]
        public long Amount { get; set; }
        [Column("fb_penalty")]
        public long? FbPenalty { get; set; }
        [Column("bs_penalty")]
        public long? BsPealty { get; set; }
        [Column("auto_bs")]
        public long? AutoBs { get; set; }
    }
}