using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Microsoft.Data.Sqlite;
using System.Reflection;

namespace Tests.HelperTests {
    [TestFixture]
    public class BillingTest {

        private const int Year1 = 2020, Year2 = 2021;
        private const int MgNr1 = 101, MgNr2 = 102, MgNr3 = 103, MgNr4 = 104;

        private const decimal
            GV_ungeb  = 0.50m,
            GV_geb    = 0.60m,
            GVB_ungeb = 0.54m,
            GVB_geb   = 0.64m,
            GVK_ungeb = 0.61m,
            GVK_geb   = 0.71m,
            WEI       = 0.10m;

        private SqliteConnection? Connection;

        [OneTimeSetUp]
        public async Task SetupDatabase() {
            Connection = await AppDbContext.ConnectAsync();
            await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.BillingInsert.sql");
        }

        [OneTimeTearDown]
        public async Task TeardownDatabase() {
            if (Connection == null) return;
            await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.BillingDelete.sql");
            await Connection.DisposeAsync();
            Connection = null;
        }

        [SetUp]
        public async Task CreatePaymentVariant() {
            var json = """
                {
                  "mode": "elwig",
                  "version": 1,
                  "payment": {
                    "GV": "curve:0",
                    "GV-B": "curve:1",
                    "GV/K": "curve:2"
                  },
                  "quality": {"WEI": 0.1},
                  "curves": [{
                    "id": 0,
                    "mode": "oe",
                    "data": {"15kmw": 0.5},
                    "geb": 0.1
                  }, {
                    "id": 1,
                    "mode": "oe",
                    "data": {"15kmw": 0.54},
                    "geb": 0.1
                  }, {
                    "id": 2,
                    "mode": "oe",
                    "data": {"15kmw": 0.61},
                    "geb": 0.1
                  }]
                }
                """;
            await InsertPaymentVariant(Year1, 1, json);
            await InsertPaymentVariant(Year2, 1, json);
        }

        [TearDown]
        public async Task CleanupDatabasePayment() {
            if (Connection == null) return;
            await AppDbContext.ExecuteBatch(Connection, """
                DELETE FROM credit;
                DELETE FROM payment_variant;
                DELETE FROM delivery_part_bucket;
                """);
        }

        private Task<Dictionary<string, AreaComBucket>> GetMemberAreaCommitmentBuckets(int year, int mgnr) {
            var ctx = new AppDbContext();
            return ctx.GetMemberAreaCommitmentBuckets(year, mgnr, Connection);
        }

        private Task<Dictionary<string, int>> GetMemberDeliveryBuckets(int year, int mgnr) {
            var ctx = new AppDbContext();
            return ctx.GetMemberDeliveryBuckets(year, mgnr, Connection);
        }

        private Task<Dictionary<string, int>> GetMemberPaymentBuckets(int year, int mgnr) {
            var ctx = new AppDbContext();
            return ctx.GetMemberPaymentBuckets(year, mgnr, Connection);
        }

        private async Task<Dictionary<(string, string), (int, decimal)>> GetMemberDeliveryPrices(int year, int mgnr) {
            var buckets = new Dictionary<(string, string), (int, decimal)>();
            using (var cmd = Connection!.CreateCommand()) {
                cmd.CommandText = $"""
                    SELECT lsnr || '/' ||  d.dpnr, d.sortid || b.discr, b.value, p.price
                    FROM v_delivery d
                        LEFT JOIN payment_delivery_part_bucket p ON (p.year, p.did, p.dpnr) = (d.year, d.did, d.dpnr)
                        LEFT JOIN delivery_part_bucket b ON (b.year, b.did, b.dpnr, b.bktnr) = (p.year, p.did, p.dpnr, p.bktnr)
                    WHERE d.year = {year} AND mgnr = {mgnr}
                    """;
                using var reader = await cmd.ExecuteReaderAsync();
                while (await reader.ReadAsync()) {
                    var lsnr = reader.GetString(0);
                    var bucket = reader.GetString(1);
                    buckets[(lsnr, bucket)] = (reader.GetInt32(2), Utils.DecFromDb(reader.GetInt32(3), 4));
                }
            }
            return buckets;
        }

        private Task InsertPaymentVariant(int year, int avnr, string data) {
            return AppDbContext.ExecuteBatch(Connection!, $"""
                INSERT INTO payment_variant (year, avnr, name, date, transfer_date, test_variant, calc_time, data)
                VALUES ({year}, {avnr}, 'Test', '2021-01-15', NULL, TRUE, NULL, '{data}');
                """);
        }

        [Test]
        public async Task Test_01_NoActiveAreaComs() {
            int mgnr = MgNr1, year = Year1;

            var areaCom = await GetMemberAreaCommitmentBuckets(year, mgnr);
            Assert.That(areaCom, Is.Empty);
            var delivery = await GetMemberDeliveryBuckets(year, mgnr);
            Assert.Multiple(() => {
                Assert.That(delivery, Has.Count.EqualTo(3));
                Assert.That(delivery["GV"],  Is.EqualTo(16_000));
                Assert.That(delivery["GV_"], Is.EqualTo( 1_000));
                Assert.That(delivery["GVK"], Is.EqualTo( 4_000));
            });

            BillingVariant b = new(year, 1);
            await b.CalculateBuckets(false, false, false);
            var payment = await GetMemberPaymentBuckets(year, mgnr);
            Assert.Multiple(() => {
                Assert.That(payment, Has.Count.EqualTo(1));
                Assert.That(payment["GV_"], Is.EqualTo(17_000));
            });

            await b.Calculate();
            var prices = await GetMemberDeliveryPrices(year, mgnr);
            Assert.Multiple(() => {
                Assert.That(prices, Has.Count.EqualTo(6));
                // Kabinett
                Assert.That(prices[("20201001X001/1", "GV_")], Is.EqualTo((4_000, GV_ungeb)));
                // ohne Attribut
                Assert.That(prices[("20201001X001/2", "GV_")], Is.EqualTo((4_000, GV_ungeb)));
                // Bio
                Assert.That(prices[("20201001X002/1", "GV_")], Is.EqualTo((4_000, GVB_ungeb)));
                // Bio
                Assert.That(prices[("20201001X002/2", "GV_")], Is.EqualTo((4_000, GVB_ungeb)));
                // ohne Attribut
                Assert.That(prices[("20201001X003/1", "GV_")], Is.EqualTo((  500, WEI)));
                // ohne Attribut
                Assert.That(prices[("20201001X003/2", "GV_")], Is.EqualTo((  500, GV_ungeb)));
            });
        }

        [Test]
        public async Task Test_02_SimpleNotStrictAreaComs() {
            int mgnr = MgNr1, year = Year2;

            var areaCom = await GetMemberAreaCommitmentBuckets(year, mgnr);
            Assert.Multiple(() => {
                Assert.That(areaCom, Has.Count.EqualTo(1));
                Assert.That(areaCom["GV"], Is.EqualTo(new AreaComBucket(10_000, 5_000, 10_000)));
            });
            var delivery = await GetMemberDeliveryBuckets(year, mgnr);
            Assert.Multiple(() => {
                Assert.That(delivery, Has.Count.EqualTo(3));
                Assert.That(delivery["GV"],  Is.EqualTo(16_000));
                Assert.That(delivery["GV_"], Is.EqualTo( 1_000));
                Assert.That(delivery["GVK"], Is.EqualTo( 4_000));
            });

            BillingVariant b = new(year, 1);
            await b.CalculateBuckets(false, false, false, Connection);
            var payment = await GetMemberPaymentBuckets(year, mgnr);
            Assert.Multiple(() => {
                Assert.That(payment, Has.Count.EqualTo(2));
                Assert.That(payment["GV_"], Is.EqualTo( 7_000));
                Assert.That(payment["GV"],  Is.EqualTo(10_000));
            });

            await b.Calculate(false, false, false);
            var prices = await GetMemberDeliveryPrices(year, mgnr);
            Assert.Multiple(() => {
                Assert.That(prices, Has.Count.EqualTo(10));
                // Kabinett
                Assert.That(prices[("20211001X001/1", "GV_")], Is.EqualTo((    0, GV_ungeb)));
                Assert.That(prices[("20211001X001/1", "GV")] , Is.EqualTo((4_000, GV_geb)));
                // ohne Attribut
                Assert.That(prices[("20211001X001/2", "GV_")], Is.EqualTo((    0, GV_ungeb)));
                Assert.That(prices[("20211001X001/2", "GV")],  Is.EqualTo((4_000, GV_geb)));
                // Bio
                Assert.That(prices[("20211001X002/1", "GV_")], Is.EqualTo((4_000, GVB_ungeb)));
                Assert.That(prices[("20211001X002/1", "GV")],  Is.EqualTo((    0, GVB_geb)));
                // Bio
                Assert.That(prices[("20211001X002/2", "GV_")], Is.EqualTo((2_000, GVB_ungeb)));
                Assert.That(prices[("20211001X002/2", "GV")],  Is.EqualTo((2_000, GVB_geb)));
                // ohne Attribut
                Assert.That(prices[("20211001X003/1", "GV_")], Is.EqualTo((  500, WEI)));
                // ohne Attribut
                Assert.That(prices[("20211001X003/2", "GV_")], Is.EqualTo((  500, GV_ungeb)));
            });
        }

        [Test]
        public async Task Test_03_SimpleNotStrictAreaComs_HonorGebunden() {
            int mgnr = MgNr1, year = Year2;

            var areaCom = await GetMemberAreaCommitmentBuckets(year, mgnr);
            Assert.Multiple(() => {
                Assert.That(areaCom, Has.Count.EqualTo(1));
                Assert.That(areaCom["GV"], Is.EqualTo(new AreaComBucket(10_000, 5_000, 10_000)));
            });
            var delivery = await GetMemberDeliveryBuckets(year, mgnr);
            Assert.Multiple(() => {
                Assert.That(delivery, Has.Count.EqualTo(3));
                Assert.That(delivery["GV"],  Is.EqualTo(16_000));
                Assert.That(delivery["GV_"], Is.EqualTo( 1_000));
                Assert.That(delivery["GVK"], Is.EqualTo( 4_000));
            });

            BillingVariant b = new(year, 1);
            await b.CalculateBuckets(true, false, false, Connection);
            var payment = await GetMemberPaymentBuckets(year, mgnr);
            Assert.Multiple(() => {
                Assert.That(payment, Has.Count.EqualTo(2));
                Assert.That(payment["GV_"], Is.EqualTo(9_000));
                Assert.That(payment["GV"],  Is.EqualTo(8_000));
            });

            await b.Calculate(true, false, false);
            var prices = await GetMemberDeliveryPrices(year, mgnr);
            Assert.Multiple(() => {
                Assert.That(prices, Has.Count.EqualTo(8));
                // Kabinett
                Assert.That(prices[("20211001X001/1", "GV_")], Is.EqualTo((    0, GV_ungeb)));
                Assert.That(prices[("20211001X001/1", "GV")],  Is.EqualTo((4_000, GV_geb)));
                // ohne Attribut
                Assert.That(prices[("20211001X001/2", "GV_")], Is.EqualTo((4_000, GV_ungeb)));
                // Bio
                Assert.That(prices[("20211001X002/1", "GV_")], Is.EqualTo((    0, GVB_ungeb)));
                Assert.That(prices[("20211001X002/1", "GV")],  Is.EqualTo((4_000, GVB_geb)));
                // Bio
                Assert.That(prices[("20211001X002/2", "GV_")], Is.EqualTo((4_000, GVB_ungeb)));
                // ohne Attribut
                Assert.That(prices[("20211001X003/1", "GV_")], Is.EqualTo((  500, WEI)));
                // ohne Attribut
                Assert.That(prices[("20211001X003/2", "GV_")], Is.EqualTo((  500, GV_ungeb)));
            });
        }

        [Test]
        [Ignore("Not implemented yet")]
        public async Task Test_04_ComplexNotStrictAreaComs() {
            // TODO
        }

        [Test]
        [Ignore("Not implemented yet")]
        public async Task Test_05_ComplexNotStrictAreaComs_HonorGebunden() {
            // TODO
        }

        [Test]
        [Ignore("Not implemented yet")]
        public async Task Test_06_StrictAreaComs_NoFillLower_NotAllowed() {
            // TODO
        }

        [Test]
        [Ignore("Not implemented yet")]
        public async Task Test_07_StrictAreaComs_NoFillLower_Allowed() {
            // TODO
        }

        [Test]
        [Ignore("Not implemented yet")]
        public async Task Test_08_StrictAreaComs_NoFillLower_Allowed_AvoidUnderDeliveries() {
            // TODO
        }

        [Test]
        [Ignore("Not implemented yet")]
        public async Task Test_09_StrictAreaComs_FillLowerUntilObligation_NotAllowed() {
            // TODO
        }

        [Test]
        [Ignore("Not implemented yet")]
        public async Task Test_10_StrictAreaComs_FillLowerUntilObligation_Allowed() {
            // TODO
        }

        [Test]
        [Ignore("Not implemented yet")]
        public async Task Test_11_StrictAreaComs_FillLowerUntilObligation_Allowed_AvoidUnderDeliveries() {
            // TODO
        }


        [Test]
        [Ignore("Not implemented yet")]
        public async Task Test_12_StrictAreaComs_FillLowerUntilObligation_NotAllowed() {
            // TODO
        }

        [Test]
        [Ignore("Not implemented yet")]
        public async Task Test_13_StrictAreaComs_FillLowerUntilObligation_Allowed() {
            // TODO
        }

        [Test]
        [Ignore("Not implemented yet")]
        public async Task Test_14_StrictAreaComs_FillLowerUntilObligation_Allowed_AvoidUnderDeliveries() {
            // TODO
        }

        [Test]
        [Ignore("Not implemented yet")]
        public async Task Test_15_StrictAreaComs_FillLowerUntilRight_NotAllowed() {
            // TODO
        }

        [Test]
        [Ignore("Not implemented yet")]
        public async Task Test_16_StrictAreaComs_FillLowerUntilRight_Allowed() {
            // TODO
        }

        [Test]
        [Ignore("Not implemented yet")]
        public async Task Test_17_StrictAreaComs_FillLowerUntilRight_Allowed_AvoidUnderDeliveries() {
            // TODO
        }
    }
}