using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Elwig.Helpers.Billing {
    public class BillingVariant : Billing {

        private readonly int AvNr;

        public BillingVariant(int year, int avnr) : base(year) {
            AvNr = avnr;
        }

        public async Task Calculate() {
            await DeleteInDb();
            await SetCalcTime();
            await CalculatePrices();
            await CalculateModifiers();
            await CalculateMemberModifiers();
        }

        public async Task Commit() {
            await Revert();
            using var cnx = await AppDbContext.ConnectAsync();
            using (var cmd = cnx.CreateCommand()) {
                // TODO modifiers and prev_modifiers
                cmd.CommandText = $"""
                    INSERT INTO credit (year, tgnr, mgnr, avnr, net_amount, prev_net_amount, vat, modifiers, prev_modifiers)
                    SELECT s.year,
                           COALESCE(t.tgnr, 0) + ROW_NUMBER() OVER(ORDER BY m.mgnr) AS tgnr,
                           m.mgnr,
                           v.avnr,
                           ROUND(p.amount / POW(10, s.precision - 2)),
                           ROUND(lp.amount / POW(10, s.precision - 2)) AS prev_amount,
                           IIF(m.buchführend, s.vat_normal, s.vat_flatrate) AS vat,
                           NULL,
                           NULL
                    FROM season s
                        JOIN payment_variant v ON v.year = s.year
                        LEFT JOIN payment_variant l ON l.year = s.year
                            AND l.avnr = (SELECT avnr
                                          FROM payment_variant
                                          WHERE NOT test_variant
                                          ORDER BY COALESCE(transfer_date, date) DESC, avnr DESC
                                          LIMIT 1)
                        LEFT JOIN (SELECT year, MAX(tgnr) AS tgnr FROM credit GROUP BY year) t ON t.year = s.year
                        JOIN (SELECT DISTINCT year, mgnr FROM delivery) d ON d.year = s.year
                        JOIN member m ON m.mgnr = d.mgnr
                        LEFT JOIN payment_member lp ON (lp.year, lp.avnr, lp.mgnr) = (l.year, l.avnr, m.mgnr)
                        LEFT JOIN payment_member p ON (p.year, p.avnr, p.mgnr) = (v.year, v.avnr, m.mgnr)
                    WHERE s.year = {Year} AND v.avnr = {AvNr}
                    """;
                await cmd.ExecuteNonQueryAsync();
            }
            using (var cmd = cnx.CreateCommand()) {
                cmd.CommandText = $"UPDATE payment_variant SET test_variant = FALSE WHERE (year, avnr) = ({Year}, {AvNr})";
                await cmd.ExecuteNonQueryAsync();
            }
        }

        public async Task Revert() {
            using var cnx = await AppDbContext.ConnectAsync();
            using (var cmd = cnx.CreateCommand()) {
                cmd.CommandText = $"DELETE FROM credit WHERE (year, avnr) = ({Year}, {AvNr})";
                await cmd.ExecuteNonQueryAsync();
            }
            using (var cmd = cnx.CreateCommand()) {
                cmd.CommandText = $"UPDATE payment_variant SET test_variant = TRUE WHERE (year, avnr) = ({Year}, {AvNr})";
                await cmd.ExecuteNonQueryAsync();
            }
        }

        protected async Task SetCalcTime() {
            using var cnx = await AppDbContext.ConnectAsync();
            using var cmd = cnx.CreateCommand();
            cmd.CommandText = $"UPDATE payment_variant SET calc_time = UNIXEPOCH() WHERE (year, avnr) = ({Year}, {AvNr})";
            await cmd.ExecuteNonQueryAsync();
        }

        protected async Task DeleteInDb() {
            using var cnx = await AppDbContext.ConnectAsync();
            using (var cmd = cnx.CreateCommand()) {
                cmd.CommandText = $"DELETE FROM payment_delivery_part_bucket WHERE (year, avnr) = ({Year}, {AvNr})";
                await cmd.ExecuteNonQueryAsync();
            }
            using (var cmd = cnx.CreateCommand()) {
                cmd.CommandText = $"DELETE FROM payment_delivery_part WHERE (year, avnr) = ({Year}, {AvNr})";
                await cmd.ExecuteNonQueryAsync();
            }
            using (var cmd = cnx.CreateCommand()) {
                cmd.CommandText = $"DELETE FROM payment_member WHERE (year, avnr) = ({Year}, {AvNr})";
                await cmd.ExecuteNonQueryAsync();
            }
            using (var cmd = cnx.CreateCommand()) {
                cmd.CommandText = $"UPDATE payment_variant SET calc_time = NULL WHERE (year, avnr) = ({Year}, {AvNr})";
                await cmd.ExecuteNonQueryAsync();
            }
        }

        protected async Task CalculateMemberModifiers() {
            using var cnx = await AppDbContext.ConnectAsync();
            if (App.Client.IsMatzen) {
                var lastYears = 3;
                var multiplier = 0.50;
                var modName = "Treue%";
                using var cmd = cnx.CreateCommand();
                cmd.CommandText = $"""
                    INSERT INTO payment_member (year, avnr, mgnr, net_amount, mod_abs, mod_rel)
                    SELECT c.year, {AvNr}, s.mgnr, 0,
                           ROUND(s.sum * COALESCE(m.abs, 0)),
                           COALESCE(m.rel, 0)
                    FROM (SELECT {Year} AS year, mgnr,
                                 ROUND(AVG(sum) * {multiplier}) AS baseline,
                                 COUNT(*) = {lastYears} AND MIN(sum) > 0 AS allowed
                          FROM v_stat_member
                          WHERE year > {Year} - {lastYears}
                          GROUP BY mgnr
                          HAVING allowed) c
                        JOIN v_stat_member s ON (s.year, s.mgnr) = (c.year, c.mgnr)
                        LEFT JOIN modifier m ON m.year = c.year AND m.name LIKE '{modName}'
                    WHERE sum >= baseline
                    ON CONFLICT DO UPDATE
                    SET mod_abs = mod_abs + excluded.mod_abs,
                        mod_rel = mod_rel + excluded.mod_rel
                    """;
                await cmd.ExecuteNonQueryAsync();
            }
        }

        protected async Task CalculatePrices() {
            using var cnx = await AppDbContext.ConnectAsync();

            var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string Discr, int Value, bool MinQuw, double Oe, double Kmw)>();
            using (var cmd = cnx.CreateCommand()) {
                cmd.CommandText = $"""
                    SELECT d.year, d.did, d.dpnr, b.bktnr, d.sortid, b.discr, b.value, d.min_quw, d.oe, d.kmw
                    FROM delivery_part_bucket b
                        JOIN v_delivery d ON (d.year, d.did, d.dpnr) = (b.year, b.did, b.dpnr)
                    WHERE b.year = {Year}
                    """;
                using var reader = await cmd.ExecuteReaderAsync();
                while (await reader.ReadAsync()) {
                    parts.Add((
                        reader.GetInt32(0), reader.GetInt32(1), reader.GetInt32(2), reader.GetInt32(3),
                        reader.GetString(4), reader.GetString(5), reader.GetInt32(6),
                        reader.GetBoolean(7), reader.GetDouble(8), reader.GetDouble(9)
                    ));
                }
            }

            var inserts = new List<(int Year, int DId, int DPNr, int BktNr, long Price, long Amount)>();
            foreach (var part in parts) {
                var price = !part.MinQuw ? 0.5m : ((part.BktNr == 2 ? 0.8m : (part.BktNr == 1 ? 0.7m : 0.6m)) + ((decimal)(part.Oe - 73) * 0.005m));  // TODO
                var priceL = Utils.DecToDb(price, 4);
                var amount = Utils.DecToDb(price * part.Value, 4);
                inserts.Add((part.Year, part.DId, part.DPNr, part.BktNr, priceL, amount));
            }

            using (var cmd = cnx.CreateCommand()) {
                cmd.CommandText = $"""
                    INSERT INTO payment_delivery_part_bucket (year, did, dpnr, bktnr, avnr, price, amount)
                    VALUES {string.Join(",\n       ", inserts.Select(i => $"({i.Year}, {i.DId}, {i.DPNr}, {i.BktNr}, {AvNr}, {i.Price}, {i.Amount})"))}
                    """;
                await cmd.ExecuteNonQueryAsync();
            }
        }

        protected async Task CalculateModifiers() {
            using var cnx = await AppDbContext.ConnectAsync();
            using var cmd = cnx.CreateCommand();
            cmd.CommandText = $"""
                INSERT INTO payment_delivery_part (year, did, dpnr, avnr, net_amount, mod_abs, mod_rel)
                SELECT d.year, d.did, d.dpnr, {AvNr}, 0, COALESCE(m.abs, 0), COALESCE(m.rel, 0)
                FROM delivery_part d
                    LEFT JOIN delivery_part_modifier p ON (p.year, p.did, p.dpnr) = (d.year, d.did, d.dpnr)
                    LEFT JOIN modifier m ON m.modid = p.modid
                WHERE d.year = {Year}
                ON CONFLICT DO UPDATE
                SET mod_abs = mod_abs + excluded.mod_abs,
                    mod_rel = mod_rel + excluded.mod_rel
                """;
            await cmd.ExecuteNonQueryAsync();
        }
    }
}