using Elwig.Helpers;
using Elwig.Models.Entities;
using Elwig.Services;
using Elwig.ViewModels;
using Microsoft.EntityFrameworkCore;

namespace Tests.UnitTests.ServiceTests {
    [TestFixture]
    public class MemberServiceTest {

        private static async Task InitViewModel(MemberAdminViewModel vm) {
            using var ctx = new AppDbContext();
            vm.BranchSource = await ctx.Branches.ToListAsync();
            vm.DefaultKgSource = await ctx.Katastralgemeinden.ToListAsync();
            vm.OrtSource = await ctx.PlzDestinations.Include(p => p.Ort).ToListAsync();
            vm.BillingOrtSource = await ctx.PlzDestinations.Include(p => p.Ort).ToListAsync();
        }

        [Test]
        public async Task TestCreate_01_Minimal() {
            var vm = new MemberAdminViewModel();
            await InitViewModel(vm);
            await vm.InitInputs();
            vm.Name = "Neuling";
            vm.GivenName = "Nadine";
            vm.Address = "Neubaugasse 1";
            vm.Plz = 2120;
            vm.Ort = vm.OrtSource.First(d => d.Ort.Name == "Wolkersdorf im Weinviertel");
            vm.BusinessShares = 1;
            vm.DefaultKg = vm.DefaultKgSource.First(k => k.Name == "Wolkersdorf");

            Assert.That(vm.MgNr, Is.EqualTo(205));
            using (var ctx = new AppDbContext()) {
                Assert.That(await ctx.Members.FindAsync(205), Is.Null);
            }

            Assert.DoesNotThrowAsync(async () => await vm.UpdateMember(null));

            Member? m;
            using (var ctx = new AppDbContext()) {
                m = await ctx.Members
                    .Where(m => m.MgNr == vm.MgNr)
                    .Include(m => m.BillingAddress!.PostalDest.AtPlz!.Ort)
                    .Include(m => m.PostalDest.AtPlz!.Ort)
                    .Include(m => m.DefaultWbKg!.AtKg)
                    .Include(m => m.EmailAddresses)
                    .Include(m => m.TelephoneNumbers)
                    .AsSplitQuery()
                    .FirstOrDefaultAsync();
            }

            Assert.That(m, Is.Not.Null);
            Assert.Multiple(() => {
                Assert.That(m.MgNr, Is.EqualTo(205));
                Assert.That(m.Name, Is.EqualTo("Neuling"));
                Assert.That(m.GivenName, Is.EqualTo("Nadine"));
                Assert.That(m.Address, Is.EqualTo("Neubaugasse 1"));
                Assert.That(m.PostalDest.AtPlz?.Plz, Is.EqualTo(2120));
                Assert.That(m.PostalDest.AtPlz?.Ort.Name, Is.EqualTo("Wolkersdorf im Weinviertel"));
                Assert.That(m.BusinessShares, Is.EqualTo(1));
                Assert.That(m.DefaultKg?.Name, Is.EqualTo("Wolkersdorf"));
            });

            vm = new MemberAdminViewModel();
            await InitViewModel(vm);
            Assert.DoesNotThrow(() => vm.FillInputs(m));
            Assert.Multiple(() => {
                Assert.That(vm.MgNr, Is.EqualTo(205));
                Assert.That(vm.Name, Is.EqualTo("Neuling"));
                Assert.That(vm.GivenName, Is.EqualTo("Nadine"));
                Assert.That(vm.Address, Is.EqualTo("Neubaugasse 1"));
                Assert.That(vm.Plz, Is.EqualTo(2120));
                Assert.That(vm.Ort?.Ort.Name, Is.EqualTo("Wolkersdorf im Weinviertel"));
                Assert.That(vm.BusinessShares, Is.EqualTo(1));
                Assert.That(vm.DefaultKg?.Name, Is.EqualTo("Wolkersdorf"));
            });
        }

        [Test]
        public async Task TestCreate_02_Full() {
            var vm = new MemberAdminViewModel();
            await InitViewModel(vm);
            await vm.InitInputs();

            vm.MgNr = 999;
            vm.IsJuridicalPerson = true;
            vm.Name = "Neue GmbH";
            vm.ForTheAttentionOf = "Norbert Neuling";
            vm.Address = "Neuegasse 2";
            vm.Plz = 2120;
            vm.Ort = vm.OrtSource.First(d => d.Ort.Name == "Wolkersdorf im Weinviertel");

            vm.EmailAddresses[0] = "neue.gmbh@mail.com";
            vm.EmailAddresses[1] = "norbert.neuling@mail.com";
            vm.PhoneNrs[0] = new(0, "+43 2245 9876", "Büro");
            vm.PhoneNrs[1] = new(1, "+43 664 123456789", "Hr. Neuling");
            vm.PhoneNrs[2] = new(2, "+43 2245 9876-2", null);

            vm.Iban = "AT97 1234 5678 9012 3460";
            vm.Bic = "RLNWATWWWDF";

            vm.UstIdNr = "ATU12345693";
            vm.LfbisNr = "0123498";
            vm.IsBuchführend = true;
            vm.IsOrganic = true;

            vm.BillingName = "Neue Holding AG";
            vm.BillingAddress = "Neuegasse 3";
            vm.BillingPlz = 2120;
            vm.BillingOrt = vm.BillingOrtSource.First(d => d.Ort.Name == "Wolkersdorf im Weinviertel");

            vm.BusinessShares = 10;
            vm.AccountingNr = "330999";
            vm.DefaultKg = vm.DefaultKgSource.First(k => k.Name == "Wolkersdorf");
            vm.Comment = "Ich bin eine Anmerkung";
            vm.ContactViaPost = true;
            vm.ContactViaEmail = true;
            vm.IsVollLieferant = true;
            vm.IsFunktionär = true;

            using (var ctx = new AppDbContext()) {
                Assert.That(await ctx.Members.FindAsync(999), Is.Null);
            }

            Assert.DoesNotThrowAsync(async () => await vm.UpdateMember(null));

            Member? m;
            using (var ctx = new AppDbContext()) {
                m = await ctx.Members
                    .Where(m => m.MgNr == vm.MgNr)
                    .Include(m => m.BillingAddress!.PostalDest.AtPlz!.Ort)
                    .Include(m => m.PostalDest.AtPlz!.Ort)
                    .Include(m => m.DefaultWbKg!.AtKg)
                    .Include(m => m.EmailAddresses)
                    .Include(m => m.TelephoneNumbers)
                    .AsSplitQuery()
                    .FirstOrDefaultAsync();
            }

            Assert.That(m, Is.Not.Null);
            Assert.Multiple(() => {
                Assert.That(m.MgNr, Is.EqualTo(999));
                Assert.That(m.IsJuridicalPerson, Is.True);
                Assert.That(m.Name, Is.EqualTo("Neue GmbH"));
                Assert.That(m.ForTheAttentionOf, Is.EqualTo("Norbert Neuling"));
                Assert.That(m.Address, Is.EqualTo("Neuegasse 2"));
                Assert.That(m.PostalDest.AtPlz?.Plz, Is.EqualTo(2120));
                Assert.That(m.PostalDest.AtPlz?.Ort.Name, Is.EqualTo("Wolkersdorf im Weinviertel"));

                Assert.That(m.EmailAddresses.Select(a => (a.Nr, a.Address)), Is.EquivalentTo(new (int, string)[] {
                    (1, "neue.gmbh@mail.com"),
                    (2, "norbert.neuling@mail.com"),
                }));
                Assert.That(m.TelephoneNumbers.Select(n => (n.Nr, n.Type, n.Number, n.Comment)), Is.EquivalentTo(new (int, string, string, string?)[] {
                    (1, "landline", "+43 2245 9876", "Büro"),
                    (2, "mobile", "+43 664 123456789", "Hr. Neuling"),
                    (3, "fax", "+43 2245 9876-2", null),
                }));

                Assert.That(m.Iban, Is.EqualTo("AT971234567890123460"));
                Assert.That(m.Bic, Is.EqualTo("RLNWATWWWDF"));

                Assert.That(m.UstIdNr, Is.EqualTo("ATU12345693"));
                Assert.That(m.LfbisNr, Is.EqualTo("0123498"));
                Assert.That(m.IsBuchführend, Is.True);
                Assert.That(m.IsOrganic, Is.True);

                Assert.That(m.BillingAddress, Is.Not.Null);
                Assert.That(m.BillingAddress?.FullName, Is.EqualTo("Neue Holding AG"));
                Assert.That(m.BillingAddress?.Address, Is.EqualTo("Neuegasse 3"));
                Assert.That(m.BillingAddress?.PostalDest.AtPlz?.Plz, Is.EqualTo(2120));
                Assert.That(m.BillingAddress?.PostalDest.AtPlz?.Ort.Name, Is.EqualTo("Wolkersdorf im Weinviertel"));

                Assert.That(m.BusinessShares, Is.EqualTo(10));
                Assert.That(m.AccountingNr, Is.EqualTo("330999"));
                Assert.That(m.DefaultKg?.Name, Is.EqualTo("Wolkersdorf"));
                Assert.That(m.Comment, Is.EqualTo("Ich bin eine Anmerkung"));
                Assert.That(m.ContactViaPost, Is.True);
                Assert.That(m.ContactViaEmail, Is.True);
                Assert.That(m.IsVollLieferant, Is.True);
                Assert.That(m.IsFunktionär, Is.True);
            });

            vm = new MemberAdminViewModel();
            await InitViewModel(vm);
            Assert.DoesNotThrow(() => vm.FillInputs(m));
            Assert.Multiple(() => {
                Assert.That(vm.MgNr, Is.EqualTo(999));
                Assert.That(vm.IsJuridicalPerson, Is.True);
                Assert.That(vm.Name, Is.EqualTo("Neue GmbH"));
                Assert.That(vm.ForTheAttentionOf, Is.EqualTo("Norbert Neuling"));
                Assert.That(vm.Address, Is.EqualTo("Neuegasse 2"));
                Assert.That(vm.Plz, Is.EqualTo(2120));
                Assert.That(vm.Ort?.Ort.Name, Is.EqualTo("Wolkersdorf im Weinviertel"));

                Assert.That(vm.EmailAddresses, Is.EquivalentTo(new string?[] {
                    "neue.gmbh@mail.com",
                    "norbert.neuling@mail.com",
                    null, null, null, null, null, null, null
                }));
                Assert.That(vm.PhoneNrs, Is.EquivalentTo(new MemberAdminViewModel.PhoneNr?[] {
                    new(0, "+43 2245 9876", "Büro"),
                    new(1, "+43 664 123456789", "Hr. Neuling"),
                    new(2, "+43 2245 9876-2", null),
                    new(), new(), new(), new(), new(), new()
                }));

                Assert.That(vm.Iban, Is.EqualTo("AT971234567890123460"));
                Assert.That(vm.Bic, Is.EqualTo("RLNWATWWWDF"));

                Assert.That(vm.UstIdNr, Is.EqualTo("ATU12345693"));
                Assert.That(vm.LfbisNr, Is.EqualTo("0123498"));
                Assert.That(vm.IsBuchführend, Is.True);
                Assert.That(vm.IsOrganic, Is.True);

                Assert.That(vm.BillingName, Is.EqualTo("Neue Holding AG"));
                Assert.That(vm.BillingAddress, Is.EqualTo("Neuegasse 3"));
                Assert.That(vm.BillingPlz, Is.EqualTo(2120));
                Assert.That(vm.BillingOrt?.Ort.Name, Is.EqualTo("Wolkersdorf im Weinviertel"));

                Assert.That(vm.BusinessShares, Is.EqualTo(10));
                Assert.That(vm.AccountingNr, Is.EqualTo("330999"));
                Assert.That(vm.DefaultKg?.Name, Is.EqualTo("Wolkersdorf"));
                Assert.That(vm.Comment, Is.EqualTo("Ich bin eine Anmerkung"));
                Assert.That(vm.ContactViaPost, Is.True);
                Assert.That(vm.ContactViaEmail, Is.True);
                Assert.That(vm.IsVollLieferant, Is.True);
                Assert.That(vm.IsFunktionär, Is.True);
            });
        }

        [Test]
        public async Task TestUpdate_01_Inactive() {
            var vm = new MemberAdminViewModel();
            await InitViewModel(vm);
            using (var ctx = new AppDbContext()) {
                vm.FillInputs(await ctx.Members
                    .Where(m => m.MgNr == 202)
                    .Include(m => m.BillingAddress!.PostalDest.AtPlz!.Ort)
                    .Include(m => m.PostalDest.AtPlz!.Ort)
                    .Include(m => m.DefaultWbKg!.AtKg)
                    .Include(m => m.EmailAddresses)
                    .Include(m => m.TelephoneNumbers)
                    .AsSplitQuery()
                    .FirstAsync());
            }

            Assert.That(vm.IsActive, Is.True);

            var exitDate = DateTime.Now;
            vm.IsActive = false;
            vm.ExitDate = $"{exitDate:dd.MM.yyyy}";

            Assert.DoesNotThrowAsync(async () => await vm.UpdateMember(202));

            Member? m;
            using (var ctx = new AppDbContext()) {
                m = await ctx.Members
                    .Where(m => m.MgNr == 202)
                    .Include(m => m.BillingAddress!.PostalDest.AtPlz!.Ort)
                    .Include(m => m.PostalDest.AtPlz!.Ort)
                    .Include(m => m.DefaultWbKg!.AtKg)
                    .Include(m => m.EmailAddresses)
                    .Include(m => m.TelephoneNumbers)
                    .AsSplitQuery()
                    .FirstOrDefaultAsync();
            }

            Assert.That(m, Is.Not.Null);
            Assert.Multiple(() => {
                Assert.That(m.IsActive, Is.False);
                Assert.That(m.ExitDateString, Is.EqualTo($"{exitDate:yyyy-MM-dd}"));
            });

            vm = new MemberAdminViewModel();
            await InitViewModel(vm);
            Assert.DoesNotThrow(() => vm.FillInputs(m));
            Assert.Multiple(() => {
                Assert.That(vm.IsActive, Is.False);
                Assert.That(vm.ExitDate, Is.EqualTo($"{exitDate:dd.MM.yyyy}"));
            });
        }

        [Test]
        public async Task TestUpdate_02_MgNr() {
            var vm = new MemberAdminViewModel();
            await InitViewModel(vm);
            using (var ctx = new AppDbContext()) {
                vm.FillInputs(await ctx.Members
                    .Where(m => m.MgNr == 203)
                    .Include(m => m.BillingAddress!.PostalDest.AtPlz!.Ort)
                    .Include(m => m.PostalDest.AtPlz!.Ort)
                    .Include(m => m.DefaultWbKg!.AtKg)
                    .Include(m => m.EmailAddresses)
                    .Include(m => m.TelephoneNumbers)
                    .AsSplitQuery()
                    .FirstAsync());
            }

            Assert.Multiple(() => {
                Assert.That(vm.MgNr, Is.EqualTo(203));
                Assert.That(vm.EmailAddresses[0], Is.Not.Null);
                Assert.That(vm.PhoneNrs[0], Is.Not.Null);
                Assert.That(vm.BillingName, Is.Not.Null);
            });
            vm.MgNr = 210;
            Assert.DoesNotThrowAsync(async () => await vm.UpdateMember(203));

            Member? m;
            using (var ctx = new AppDbContext()) {
                m = await ctx.Members
                    .Where(m => m.MgNr == 210)
                    .Include(m => m.BillingAddress!.PostalDest.AtPlz!.Ort)
                    .Include(m => m.PostalDest.AtPlz!.Ort)
                    .Include(m => m.DefaultWbKg!.AtKg)
                    .Include(m => m.EmailAddresses)
                    .Include(m => m.TelephoneNumbers)
                    .AsSplitQuery()
                    .FirstOrDefaultAsync();
            }

            Assert.That(m, Is.Not.Null);
            vm = new MemberAdminViewModel();
            await InitViewModel(vm);
            Assert.DoesNotThrow(() => vm.FillInputs(m));
            Assert.Multiple(() => {
                Assert.That(vm.MgNr, Is.EqualTo(210));
                Assert.That(vm.EmailAddresses[0], Is.Not.Null);
                Assert.That(vm.PhoneNrs[0], Is.Not.Null);
                Assert.That(vm.BillingName, Is.Not.Null);
            });
        }

        [Test]
        public async Task TestDelete_01_NoReferences() {
            using (var ctx = new AppDbContext()) {
                Assert.That(await ctx.Members.FindAsync(201), Is.Not.Null);
            }
            Assert.DoesNotThrowAsync(async () => await MemberService.DeleteMember(201, false, false, false));
            using (var ctx = new AppDbContext()) {
                Assert.That(await ctx.Members.FindAsync(201), Is.Null);
            }
        }

        [Test]
        public async Task TestDelete_02_AllReferences() {
            using (var ctx = new AppDbContext()) {
                Assert.That(await ctx.Members.FindAsync(204), Is.Not.Null);
            }
            for (int i = 0; i < 7; i++) {
                Assert.ThrowsAsync<DbUpdateException>(async () => await MemberService.DeleteMember(204, (i & 1) != 0, (i & 2) != 0, (i & 4) != 0));
                using var ctx = new AppDbContext();
                Assert.That(await ctx.Members.FindAsync(204), Is.Not.Null);
            }
            Assert.DoesNotThrowAsync(async () => await MemberService.DeleteMember(204, true, true, true));
            using (var ctx = new AppDbContext()) {
                Assert.That(await ctx.Members.FindAsync(204), Is.Null);
            }
        }
    }
}
