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 = 2020;
        private const int MgNr1 = 101, MgNr2 = 102, MgNr3 = 103, MgNr4 = 104;

        private SqliteConnection? Connection;

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

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

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

        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>> GetMemberDeliveryPrices(int year, int mgnr) {
            var buckets = new Dictionary<(string, string), int>();
            using (var cmd = Connection!.CreateCommand()) {
                cmd.CommandText = $"""
                    SELECT lsnr || '/' ||  d.dpnr, d.sortid || b.discr, 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);
                }
            }
            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_NoActiveAreaCommitments() {
            int mgnr = MgNr1, year = Year1;
            await InsertPaymentVariant(year, 1, """
                {
                  "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
                  }]
                }
                """);

            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(4));
                Assert.That(delivery["GV"],  Is.EqualTo(16_000));
                Assert.That(delivery["GV_"], Is.EqualTo( 1_000));
                Assert.That(delivery["GVB"], Is.EqualTo( 8_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));
                Assert.That(prices[("20201001X001/1", "GV_")], Is.EqualTo(0_6100));
                Assert.That(prices[("20201001X001/2", "GV_")], Is.EqualTo(0_5000));
                Assert.That(prices[("20201001X002/1", "GV_")], Is.EqualTo(0_5400));
                Assert.That(prices[("20201001X002/2", "GV_")], Is.EqualTo(0_5400));
                Assert.That(prices[("20201001X003/1", "GV_")], Is.EqualTo(0_1000));
                Assert.That(prices[("20201001X003/2", "GV_")], Is.EqualTo(0_5000));
            });
        }
    }
}