using e_suite.Database.Core.Tables.CustomFields;
using e_suite.Database.Core.Tables.Forms;
using e_suite.Database.Core.Tables.Printer;
using e_suite.Service.EFlowSync.EflowAPI;
using e_suite.Service.EFlowSync.UnitTests.Helpers;
using eSuite.Core.CustomFields;
using eSuite.Core.Miscellaneous;
using Moq;
using NUnit.Framework;

namespace e_suite.Service.EFlowSync.UnitTests.EFlowSync
{
    public class SyncEFlowPrinterCategoriesUnitTests : SyncEFlowTestBase
    {


        [SetUp]
        public async Task SetUp()
        {
            await base.Setup();
        }

        [Test]
        public void SyncEFlowPrinterCategories_NoDomainsToSync_DoesNothing()
        {
            //Arrange
            Domains.Clear();
            Assert.That(DomainsToLookup.Data.Count(), Is.Zero);

            //Act & Assert
            Assert.DoesNotThrowAsync(async() => await EFlowSync.SyncEFlowPrinterCategories(null!));
        }

        [Test]
        public void SyncEFlowPrinterCategories_NullPassedAsParameter_ThrowsNullReferenceException()
        {
            //Arrange

            //Act & Assert
            Assert.ThrowsAsync<NullReferenceException>( async () => await EFlowSync.SyncEFlowPrinterCategories(null!));
        }

        [Test]
        public async Task SyncEFlowPrinterCategories_EmptyListPassedAsParameter_ThrowsNullReferenceException()
        {
            //Arrange
            foreach (var domain in Domains)
            {
                domain.SunriseAppId = string.Empty; //makes sure that all domains are not setup for syncing with e-flow.
            }

            var domainsToSync = new List<GeneralIdRef>();

            //Act & Assert
            await EFlowSync.SyncEFlowPrinterCategories(domainsToSync);
        }

        [Test]
        public void SyncEFlowPrinterCategories_FailsToGetCategoryFromEflow_ThrowsExpectedException()
        {
            //Arrange
            var domainsToSync = new List<GeneralIdRef>();

            //Act
            var exception = Assert.ThrowsAsync<NullReferenceException>( async() => await EFlowSync.SyncEFlowPrinterCategories(domainsToSync));

            //Assert
            Assert.That(exception!.Message, Is.EqualTo("Unable to find e-flow printer category"));
        }

        [Test]
        public void SyncEFlowPrinterCategories_RequestToUpdateNonExistingDomain_DoesNothingGracefully()
        {
            //Arrange
            var domainsToSync = new List<GeneralIdRef>
            {
                new() { Guid = new Guid("{497AAEE7-496A-468C-A6A3-4C003E815CE6}") }
            };

            //Act & Assert
            Assert.DoesNotThrowAsync(async () => await EFlowSync.SyncEFlowPrinterCategories(domainsToSync));
        }

        [Test]
        public async Task SyncEFlowPrinterCategories_WhenCategoryDoesNotContainAnything_AddsExtraCategoryToEFlow()
        {
            //Arrange
            var domainsToSync = new List<GeneralIdRef>();

            var testCategory = new CategoryInfo
            {
                Id = "Category1",
                DisplayName = "e-print printers"
            };

            EFlowCategoriesMock.Setup(x => x.GetCategories("Category1")).ReturnsAsync(testCategory);

            FakeFormRepository.FormInstances.Add(new FormInstance
                {
                    Id = 1000,
                    FormFields = new List<FormFieldInstance>
                    {
                        new()
                        {
                            Id = 100,
                            CustomField = new CustomField
                            {
                                Guid = new Guid("{45821c2d-9d6e-4c82-ac57-965755960422}"),
                                FieldType = FieldType.Domain,
                                MaxEntries = 0
                            },
                            Index = 1,
                            Value = "63cac905-acea-48d4-9faa-25deb34de123"
                        }
                    }
                }
            );

            FakeSpecificationManagerRepository.Specifications.Add( new Specification
            {
                FormInstanceId = 1000,
                Name = "TestSpecification",
                Site = new Site
                {
                    Id = 2,
                    Name = "MySite",
                    Organisation = new Organisation
                    {
                        Id = 3,
                        Name = "MyOrganisation"
                    }
                }
            });

            //Act
            await EFlowSync.SyncEFlowPrinterCategories(domainsToSync);

            //Assert
            EFlowCategoriesMock.Verify(x => x.PostCategory(It.IsAny<CategoryInfo>()), Times.Exactly(3));
            EFlowCategoriesMock.Verify(x => x.DeleteCategory(It.IsAny<CategoryInfo>()), Times.Never);
            EFlowCategoriesMock.Verify(x => x.UpdateCategory(It.IsAny<CategoryInfo>()), Times.Never);
            EFlowCategoriesMock.Verify(x => x.RestoreCategory(It.IsAny<CategoryInfo>()), Times.Never);

            Assert.That(testCategory.Children.Count, Is.EqualTo(1));
            var organisationCategory = testCategory.Children[0];
            Assert.That(organisationCategory.DisplayName, Is.EqualTo("MyOrganisation"));
            Assert.That(organisationCategory.Children.Count, Is.EqualTo(1));
            var siteCategory = organisationCategory.Children[0];
            Assert.That(siteCategory.DisplayName, Is.EqualTo("MySite"));
            Assert.That(siteCategory.Children.Count, Is.EqualTo(1));
            var specificationCategory = siteCategory.Children[0];
            Assert.That(specificationCategory.DisplayName, Is.EqualTo("TestSpecification"));
            Assert.That(specificationCategory.Children.Count, Is.Zero);
        }


        [Test]
        public async Task SyncEFlowPrinterCategories_WhenOrganisationSiteAndSpecAlreadyExists_ChangesNothing()
        {
            //Arrange
            var domainsToSync = new List<GeneralIdRef>();

            var testCategory = new CategoryInfo
            {
                Id = "Category1",
                DisplayName = "e-print printers",
                Children =
                [
                    new CategoryInfo
                    {
                        Id = "Org1",
                        DisplayName = "MyOrganisation",
                        Children = [
                            new CategoryInfo
                            {
                                Id = "Site1",
                                DisplayName = "MySite",
                                Children = [
                                    new CategoryInfo
                                    {
                                        Id = "Spec1",
                                        DisplayName = "TestSpecification"
                                    }
                                ]
                            }
                        ]
                    }
                ]
            };

            EFlowCategoriesMock.Setup(x => x.GetCategories("Category1")).ReturnsAsync(testCategory);

            FakeFormRepository.FormInstances.Add(new FormInstance
            {
                Id = 1000,
                FormFields = new List<FormFieldInstance>
                {
                    new()
                    {
                        Id = 100,
                        CustomField = new CustomField
                        {
                            Guid = new Guid("{45821c2d-9d6e-4c82-ac57-965755960422}"),
                            FieldType = FieldType.Domain,
                            MaxEntries = 0
                        },
                        Index = 1,
                        Value = "63cac905-acea-48d4-9faa-25deb34de123"
                    }
                }
            });

            var commonSite = new Site
            {
                Id = 2,
                Name = "MySite",
                Organisation = new Organisation
                {
                    Id = 3,
                    Name = "MyOrganisation"
                }
            };

            FakeSpecificationManagerRepository.Specifications.Add(new Specification
            {
                Id = 10000,
                FormInstanceId = 1000,
                Name = "TestSpecification",
                Site = commonSite
            });

            //Act
            await EFlowSync.SyncEFlowPrinterCategories(domainsToSync);

            //Assert
            EFlowCategoriesMock.Verify(x => x.PostCategory(It.IsAny<CategoryInfo>()), Times.Never);
            EFlowCategoriesMock.Verify(x => x.DeleteCategory(It.IsAny<CategoryInfo>()), Times.Never);
            EFlowCategoriesMock.Verify(x => x.UpdateCategory(It.IsAny<CategoryInfo>()), Times.Never);
            EFlowCategoriesMock.Verify(x => x.RestoreCategory(It.IsAny<CategoryInfo>()), Times.Never);
        }

        [Test]
        public async Task SyncEFlowPrinterCategories_WhenNewSpecificationAddedToSite_AddsOnlyNewSpec()
        {
            //Arrange
            var domainsToSync = new List<GeneralIdRef>();

            var testCategory = new CategoryInfo
            {
                Id = "Category1",
                DisplayName = "e-print printers",
                Children =
                [
                    new CategoryInfo
                    {
                        Id = "Org1",
                        DisplayName = "MyOrganisation",
                        Children = [
                            new CategoryInfo
                            {
                                Id = "Site1",
                                DisplayName = "MySite",
                                Children = [
                                    new CategoryInfo
                                    {
                                        Id = "Spec1",
                                        DisplayName = "TestSpecification"
                                    }
                                ]
                            }
                        ]
                    }
                ]
            };

            EFlowCategoriesMock.Setup(x => x.GetCategories("Category1")).ReturnsAsync(testCategory);

            FakeFormRepository.FormInstances.Add(new FormInstance
            {
                Id = 1000,
                FormFields = new List<FormFieldInstance>
                {
                    new()
                    {
                        Id = 100,
                        CustomField = new CustomField
                        {
                            Guid = new Guid("{45821c2d-9d6e-4c82-ac57-965755960422}"),
                            FieldType = FieldType.Domain,
                            MaxEntries = 0
                        },
                        Index = 1,
                        Value = "63cac905-acea-48d4-9faa-25deb34de123"
                    }
                }
            });
            FakeFormRepository.FormInstances.Add(new FormInstance
            {
                Id = 1001,
                FormFields = new List<FormFieldInstance>
                {
                    new()
                    {
                        Id = 100,
                        CustomField = new CustomField
                        {
                            Guid = new Guid("{45821c2d-9d6e-4c82-ac57-965755960422}"),
                            FieldType = FieldType.Domain,
                            MaxEntries = 0
                        },
                        Index = 1,
                        Value = "63cac905-acea-48d4-9faa-25deb34de123"
                    }
                }
            });

            var commonSite = new Site
            {
                Id = 2,
                Name = "MySite",
                Organisation = new Organisation
                {
                    Id = 3,
                    Name = "MyOrganisation"
                }
            };

            FakeSpecificationManagerRepository.Specifications.Add(new Specification
            {
                Id = 10000,
                FormInstanceId = 1000,
                Name = "TestSpecification",
                Site = commonSite
            });
            FakeSpecificationManagerRepository.Specifications.Add(new Specification
            {
                Id = 10001,
                FormInstanceId = 1000,
                Name = "TestSpecification1",
                Site = commonSite
            });

            //Act
            await EFlowSync.SyncEFlowPrinterCategories(domainsToSync);

            //Assert
            EFlowCategoriesMock.Verify(x => x.PostCategory(It.IsAny<CategoryInfo>()), Times.Once);
            EFlowCategoriesMock.Verify(x => x.DeleteCategory(It.IsAny<CategoryInfo>()), Times.Never);
            EFlowCategoriesMock.Verify(x => x.UpdateCategory(It.IsAny<CategoryInfo>()), Times.Never);
            EFlowCategoriesMock.Verify(x => x.RestoreCategory(It.IsAny<CategoryInfo>()), Times.Never);
        }

        [Test]
        public async Task SyncEFlowPrinterCategories_WhenSpecificationRemoved_DeletesCorrectSpecification()
        {
            //Arrange
            var domainsToSync = new List<GeneralIdRef>();

            var testCategory = new CategoryInfo
            {
                Id = "Category1",
                DisplayName = "e-print printers",
                Children =
                [
                    new CategoryInfo
                    {
                        Id = "Org1",
                        DisplayName = "MyOrganisation",
                        Children = [
                            new CategoryInfo
                            {
                                Id = "Site1",
                                DisplayName = "MySite",
                                Children = [
                                    new CategoryInfo
                                    {
                                        Id = "Spec1",
                                        DisplayName = "TestSpecification"
                                    }
                                ]
                            }
                        ]
                    },
                    new CategoryInfo
                    {
                        Id = "Org2",
                        DisplayName = "MyOrganisation2",
                        Children = [
                            new CategoryInfo
                            {
                                Id = "Site2",
                                DisplayName = "MySite2",
                                Children = [
                                    new CategoryInfo
                                    {
                                        Id = "Spec2",
                                        DisplayName = "TestSpecification2"
                                    }
                                ]
                            }
                        ]
                    }
                ]
            };

            EFlowCategoriesMock.Setup(x => x.GetCategories("Category1")).ReturnsAsync(testCategory);

            FakeFormRepository.FormInstances.Add(new FormInstance
            {
                Id = 1000,
                FormFields = new List<FormFieldInstance>
                {
                    new()
                    {
                        Id = 100,
                        CustomField = new CustomField
                        {
                            Guid = new Guid("{45821c2d-9d6e-4c82-ac57-965755960422}"),
                            FieldType = FieldType.Domain,
                            MaxEntries = 0
                        },
                        Index = 1,
                        Value = "63cac905-acea-48d4-9faa-25deb34de123"
                    }
                }
            });

            var commonSite = new Site
            {
                Id = 2,
                Name = "MySite",
                Organisation = new Organisation
                {
                    Id = 3,
                    Name = "MyOrganisation"
                }
            };

            FakeSpecificationManagerRepository.Specifications.Add(new Specification
            {
                Id = 10000,
                FormInstanceId = 1000,
                Name = "TestSpecification",
                Site = commonSite
            });

            //Act
            await EFlowSync.SyncEFlowPrinterCategories(domainsToSync);

            //Assert
            EFlowCategoriesMock.Verify(x => x.PostCategory(It.IsAny<CategoryInfo>()), Times.Never);
            EFlowCategoriesMock.Verify(x => x.DeleteCategory(It.IsAny<CategoryInfo>()), Times.Exactly(3));
            EFlowCategoriesMock.Verify(x => x.UpdateCategory(It.IsAny<CategoryInfo>()), Times.Never);
            EFlowCategoriesMock.Verify(x => x.RestoreCategory(It.IsAny<CategoryInfo>()), Times.Never);
        }

        [Test]
        public async Task SyncEFlowPrinterCategories_WhenSpecificationNameChanges_UpdatedExistingSpecification()
        {
            //Arrange
            var domainsToSync = new List<GeneralIdRef>();

            var testCategory = new CategoryInfo
            {
                Id = "Category1",
                DisplayName = "e-print printers",
                Children =
                [
                    new CategoryInfo
                    {
                        Id = "Org1",
                        DisplayName = "MyOrganisation",
                        Children = [
                            new CategoryInfo
                            {
                                Id = "Site1",
                                DisplayName = "MySite",
                                Children = [
                                    new CategoryInfo
                                    {
                                        Id = "Spec1",
                                        DisplayName = "TestSpecification Renamed"
                                    }
                                ]
                            }
                        ]
                    }
                ]
            };

            EFlowCategoriesMock.Setup(x => x.GetCategories("Category1")).ReturnsAsync(testCategory);

            FakeFormRepository.FormInstances.Add(new FormInstance
            {
                Id = 1000,
                FormFields = new List<FormFieldInstance>
                {
                    new()
                    {
                        Id = 100,
                        CustomField = new CustomField
                        {
                            Guid = new Guid("{45821c2d-9d6e-4c82-ac57-965755960422}"),
                            FieldType = FieldType.Domain,
                            MaxEntries = 0
                        },
                        Index = 1,
                        Value = "63cac905-acea-48d4-9faa-25deb34de123"
                    }
                }
            });

            var commonSite = new Site
            {
                Id = 2,
                Name = "MySite",
                Organisation = new Organisation
                {
                    Id = 3,
                    Name = "MyOrganisation"
                }
            };

            FakeSpecificationManagerRepository.Specifications.Add(new Specification
            {
                Id = 10000,
                FormInstanceId = 1000,
                Name = "TestSpecification",
                Site = commonSite
            });

            FakeEFlowSyncRepository.ExternalIds.Add(("e_suite.Database.Core.Tables.Printer.Organisation", "{\"Id\":2}"), "Org1");
            FakeEFlowSyncRepository.ExternalIds.Add(("e_suite.Database.Core.Tables.Printer.Site", "{\"Id\":2}"), "Site1");
            FakeEFlowSyncRepository.ExternalIds.Add(("e_suite.Database.Core.Tables.Printer.Specification", "{\"Id\":10000}"), "Spec1");

            //Act
            await EFlowSync.SyncEFlowPrinterCategories(domainsToSync);

            //Assert
            EFlowCategoriesMock.Verify(x => x.PostCategory(It.IsAny<CategoryInfo>()), Times.Never);
            EFlowCategoriesMock.Verify(x => x.DeleteCategory(It.IsAny<CategoryInfo>()), Times.Never);
            EFlowCategoriesMock.Verify(x => x.UpdateCategory(It.IsAny<CategoryInfo>()), Times.Once);
            EFlowCategoriesMock.Verify(x => x.RestoreCategory(It.IsAny<CategoryInfo>()), Times.Never);
        }

        [Test]
        public async Task SyncEFlowPrinterCategories_WhenSpecificationIsRestored_RestoresCategory()
        {
            //Arrange
            var domainsToSync = new List<GeneralIdRef>();

            var testCategory = new CategoryInfo
            {
                Id = "Category1",
                DisplayName = "e-print printers",
                Children =
                [
                    new CategoryInfo
                    {
                        Id = "Org1",
                        DisplayName = "MyOrganisation",
                        Children = [
                            new CategoryInfo
                            {
                                Id = "Site1",
                                DisplayName = "MySite",
                                Children = [
                                    new CategoryInfo
                                    {
                                        Id = "Spec1",
                                        DisplayName = "TestSpecification",
                                        Deleted = new DateTimeOffset(2025, 01, 01, 00,00,00, TimeSpan.Zero)
                                    }
                                ]
                            }
                        ]
                    }
                ]
            };

            EFlowCategoriesMock.Setup(x => x.GetCategories("Category1")).ReturnsAsync(testCategory);

            FakeFormRepository.FormInstances.Add(new FormInstance
            {
                Id = 1000,
                FormFields = new List<FormFieldInstance>
                {
                    new()
                    {
                        Id = 100,
                        CustomField = new CustomField
                        {
                            Guid = new Guid("{45821c2d-9d6e-4c82-ac57-965755960422}"),
                            FieldType = FieldType.Domain,
                            MaxEntries = 0
                        },
                        Index = 1,
                        Value = "63cac905-acea-48d4-9faa-25deb34de123"
                    }
                }
            });

            var commonSite = new Site
            {
                Id = 2,
                Name = "MySite",
                Organisation = new Organisation
                {
                    Id = 3,
                    Name = "MyOrganisation"
                }
            };

            FakeSpecificationManagerRepository.Specifications.Add(new Specification
            {
                Id = 10000,
                FormInstanceId = 1000,
                Name = "TestSpecification",
                Site = commonSite
            });

            FakeEFlowSyncRepository.ExternalIds.Add(("e_suite.Database.Core.Tables.Printer.Organisation", "{\"Id\":2}"), "Org1");
            FakeEFlowSyncRepository.ExternalIds.Add(("e_suite.Database.Core.Tables.Printer.Site", "{\"Id\":2}"), "Site1");
            FakeEFlowSyncRepository.ExternalIds.Add(("e_suite.Database.Core.Tables.Printer.Specification", "{\"Id\":10000}"), "Spec1");

            //Act
            await EFlowSync.SyncEFlowPrinterCategories(domainsToSync);

            //Assert
            EFlowCategoriesMock.Verify(x => x.PostCategory(It.IsAny<CategoryInfo>()), Times.Never);
            EFlowCategoriesMock.Verify(x => x.DeleteCategory(It.IsAny<CategoryInfo>()), Times.Never);
            EFlowCategoriesMock.Verify(x => x.UpdateCategory(It.IsAny<CategoryInfo>()), Times.Never);
            EFlowCategoriesMock.Verify(x => x.RestoreCategory(It.IsAny<CategoryInfo>()), Times.Once);
        }

        [Test]
        public async Task SyncEFlowPrinterCategories_WhenAttemptingToDeleteAlreadyDeletedSpeciciation_DoesNothing()
        {
            //Arrange
            var domainsToSync = new List<GeneralIdRef>();

            var testCategory = new CategoryInfo
            {
                Id = "Category1",
                DisplayName = "e-print printers",
                Children =
                [
                    new CategoryInfo
                    {
                        Id = "Org1",
                        DisplayName = "MyOrganisation",
                        Children = [
                            new CategoryInfo
                            {
                                Id = "Site1",
                                DisplayName = "MySite",
                                Children = [
                                    new CategoryInfo
                                    {
                                        Id = "Spec1",
                                        DisplayName = "TestSpecification"
                                    }
                                ]
                            }
                        ]
                    },
                    new CategoryInfo
                    {
                        Id = "Org2",
                        DisplayName = "MyOrganisation2",
                        Deleted = new DateTimeOffset(2025, 01, 01, 00,00,00, TimeSpan.Zero),
                        Children = [
                            new CategoryInfo
                            {
                                Id = "Site2",
                                DisplayName = "MySite2",
                                Deleted = new DateTimeOffset(2025, 01, 01, 00,00,00, TimeSpan.Zero),
                                Children = [
                                    new CategoryInfo
                                    {
                                        Id = "Spec2",
                                        DisplayName = "TestSpecification2",
                                        Deleted = new DateTimeOffset(2025, 01, 01, 00,00,00, TimeSpan.Zero)
                                    }
                                ]
                            }
                        ]
                    }
                ]
            };

            EFlowCategoriesMock.Setup(x => x.GetCategories("Category1")).ReturnsAsync(testCategory);

            FakeFormRepository.FormInstances.Add(new FormInstance
            {
                Id = 1000,
                FormFields = new List<FormFieldInstance>
                {
                    new()
                    {
                        Id = 100,
                        CustomField = new CustomField
                        {
                            Guid = new Guid("{45821c2d-9d6e-4c82-ac57-965755960422}"),
                            FieldType = FieldType.Domain,
                            MaxEntries = 0
                        },
                        Index = 1,
                        Value = "63cac905-acea-48d4-9faa-25deb34de123"
                    }
                }
            });

            var commonSite = new Site
            {
                Id = 2,
                Name = "MySite",
                Organisation = new Organisation
                {
                    Id = 3,
                    Name = "MyOrganisation"
                }
            };

            FakeSpecificationManagerRepository.Specifications.Add(new Specification
            {
                Id = 10000,
                FormInstanceId = 1000,
                Name = "TestSpecification",
                Site = commonSite
            });

            //Act
            await EFlowSync.SyncEFlowPrinterCategories(domainsToSync);

            //Assert
            EFlowCategoriesMock.Verify(x => x.PostCategory(It.IsAny<CategoryInfo>()), Times.Never);
            EFlowCategoriesMock.Verify(x => x.DeleteCategory(It.IsAny<CategoryInfo>()), Times.Never);
            EFlowCategoriesMock.Verify(x => x.UpdateCategory(It.IsAny<CategoryInfo>()), Times.Never);
            EFlowCategoriesMock.Verify(x => x.RestoreCategory(It.IsAny<CategoryInfo>()), Times.Never);
        }
    }
}
