using e_suite.Database.Audit.AuditEngine;
using e_suite.Database.Audit.UnitTests.Helpers;
using e_suite.Database.Audit.UnitTests.Helpers.Tables;
using NUnit.Framework;

namespace e_suite.Database.Audit.UnitTests.AuditEngineCore;

[TestFixture]
public class SoftDeleteUnitTests : AuditEngineCoreTestBase
{
    [Test]
    public async Task AddRow_ItemEditedHasNotDeleted_AuditEntryShowsAsUpdate()
    {
        //Arrange
        var auditUserDetails = new AuditUserDetails
        {
            UserId = 1,
            UserDisplayName = "Testy McTester",
            Comment = "Test comment"
        };

        var leafTreeValue = new DeletableTreeValue
        {
            Id = 2000,
            Name = "Leaf Level item",
            Deleted = false
        };

        testDBContext.DeletableTreeValues.Add(leafTreeValue);
        await testDBContext.NoAuditSaveChangesAsync();

        //Act
        var itemToEdit = testDBContext.DeletableTreeValues.Single(x => x.Id == leafTreeValue.Id);
        itemToEdit.Name = "Altered Name";
        itemToEdit.Deleted = false;
        await testDBContext.SaveChangesAsync(auditUserDetails);

        //Assert
        Assert.That(testDBContext.AuditDetails.Count(), Is.EqualTo(1));
        var auditDetail = testDBContext.AuditDetails.First();
        Assert.That(auditDetail.Type, Is.EqualTo(AuditType.Update.ToString()));
    }

    [Test]
    public async Task AddRow_ItemIsUndeleted_AuditEntryShowsAsRestore()
    {
        //Arrange
        var auditUserDetails = new AuditUserDetails
        {
            UserId = 1,
            UserDisplayName = "Testy McTester",
            Comment = "Test comment"
        };

        var leafTreeValue = new DeletableTreeValue
        {
            Id = 2000,
            Name = "Leaf Level item",
            Deleted = true
        };

        testDBContext.DeletableTreeValues.Add(leafTreeValue);
        await testDBContext.NoAuditSaveChangesAsync();

        //Act
        var itemToEdit = testDBContext.DeletableTreeValues.Single(x => x.Id == leafTreeValue.Id);
        itemToEdit.Name = "Altered Name";
        itemToEdit.Deleted = false;
        await testDBContext.SaveChangesAsync(auditUserDetails);

        //Assert
        Assert.That(testDBContext.AuditDetails.Count(), Is.EqualTo(1));
        var auditDetail = testDBContext.AuditDetails.First();
        Assert.That(auditDetail.Type, Is.EqualTo(AuditType.Restore.ToString()));
    }

    [TestCase(true)] //Soft delete
    [TestCase(false)] //Undelete
    public async Task AddRow_TableHasSelfReferenceAndReferenceNull_AuditForSoftDeleteOrUndeleteRecorded(bool deleteRow)
    {
        //Arrange
        var auditUserDetails = new AuditUserDetails
        {
            UserId = 1,
            UserDisplayName = "Testy McTester",
            Comment = "Test comment"
        };

        var leafTreeValue = new DeletableTreeValue
        {
            Id = 30000,
            Name = "Leaf Level item",
            Deleted = !deleteRow
        };

        testDBContext.DeletableTreeValues.Add(leafTreeValue);
        await testDBContext.NoAuditSaveChangesAsync();
        
        //Act
        var itemToEdit = testDBContext.DeletableTreeValues.Single(x => x.Id == leafTreeValue.Id);
        itemToEdit.Deleted = deleteRow;
        await testDBContext.SaveChangesAsync(auditUserDetails);
        
        //Assert
        Assert.That(testDBContext.AuditDetails.Count(), Is.EqualTo(1));
        var auditDetail = testDBContext.AuditDetails.First();
        Assert.That(auditDetail.UserDisplayName, Is.EqualTo(auditUserDetails.UserDisplayName));
        Assert.That(auditDetail.UserId, Is.EqualTo(auditUserDetails.UserId));
        Assert.That(auditDetail.Comment, Is.EqualTo(auditUserDetails.Comment));

        Assert.That(auditDetail.Id, Is.EqualTo(1));

        Assert.That(auditDetail.Type, Is.EqualTo(deleteRow ? AuditType.Delete.ToString() : AuditType.Restore.ToString()));

        Assert.That(auditDetail.Fields, Is.EqualTo( string.Empty));

        Assert.That(testDBContext.AuditEntries.Count(), Is.EqualTo(1));

        var auditEntries = testDBContext.AuditEntries.ToList();
        var childAuditEntry = auditEntries[0];
        Assert.That(childAuditEntry.Id, Is.EqualTo(1));
        Assert.That(childAuditEntry.EntityName, Is.EqualTo(typeof(DeletableTreeValue).FullName));
        Assert.That(childAuditEntry.DisplayName, Is.EqualTo(leafTreeValue.Name));
        Assert.That(childAuditEntry.AuditLogId, Is.EqualTo(auditDetail.Id));
        Assert.That(childAuditEntry.PrimaryKey, Is.EqualTo($"{{\"Id\":{leafTreeValue.Id}}}"));
        Assert.That(childAuditEntry.IsPrimary, Is.True);

        Assert.That(testDBContext.AuditDrillHierarchies.Count(), Is.EqualTo(0));
    }

    [TestCase(true)] //Soft delete
    [TestCase(false)] //Undelete
    public async Task AddRow_TableHasSingleParentItem_AuditForSoftDeleteOrUndeleteRecorded(bool deleteRow)
    {
        //Arrange
        var auditUserDetails = new AuditUserDetails
        {
            UserId = 1,
            UserDisplayName = "Testy McTester",
            Comment = "Test comment"
        };

        var rootTreeValue = new DeletableTreeValue
        {
            Id = 3000,
            Name = "Root Level item",
            Deleted = !deleteRow
        };

        var leafTreeValue = new DeletableTreeValue
        {
            Id = 30000,
            Name = "Leaf Level item",
            Deleted = !deleteRow,
            ParentId = rootTreeValue.Id
        };

        testDBContext.DeletableTreeValues.Add(rootTreeValue);
        testDBContext.DeletableTreeValues.Add(leafTreeValue);
        await testDBContext.NoAuditSaveChangesAsync();

        //Act
        var itemToEdit = testDBContext.DeletableTreeValues.Single(x => x.Id == leafTreeValue.Id);
        itemToEdit.Deleted = deleteRow;
        await testDBContext.SaveChangesAsync(auditUserDetails);

        //Assert
        Assert.That(testDBContext.AuditDetails.Count(), Is.EqualTo(1));
        var auditDetail = testDBContext.AuditDetails.First();
        Assert.That(auditDetail.UserDisplayName, Is.EqualTo(auditUserDetails.UserDisplayName));
        Assert.That(auditDetail.UserId, Is.EqualTo(auditUserDetails.UserId));
        Assert.That(auditDetail.Comment, Is.EqualTo(auditUserDetails.Comment));

        Assert.That(auditDetail.Id, Is.EqualTo(1));

        Assert.That(auditDetail.Type, Is.EqualTo(deleteRow ? AuditType.Delete.ToString() : AuditType.Restore.ToString()));
        
        Assert.That(testDBContext.DeletableTreeValues.Count(), Is.EqualTo(2));

        Assert.That(testDBContext.AuditEntries.Count(), Is.EqualTo(2));

        var auditEntries = testDBContext.AuditEntries.ToList();
        var childAuditEntry = auditEntries[0];
        Assert.That(childAuditEntry.Id, Is.EqualTo(1));
        Assert.That(childAuditEntry.EntityName, Is.EqualTo(typeof(DeletableTreeValue).FullName));
        Assert.That(childAuditEntry.DisplayName, Is.EqualTo(leafTreeValue.Name));
        Assert.That(childAuditEntry.AuditLogId, Is.EqualTo(auditDetail.Id));
        Assert.That(childAuditEntry.PrimaryKey, Is.EqualTo($"{{\"Id\":{leafTreeValue.Id}}}"));
        Assert.That(childAuditEntry.IsPrimary, Is.True);

        var parentAuditEntry = auditEntries[1];
        Assert.That(parentAuditEntry.Id, Is.EqualTo(2));
        Assert.That(parentAuditEntry.EntityName, Is.EqualTo(typeof(DeletableTreeValue).FullName));
        Assert.That(parentAuditEntry.DisplayName, Is.EqualTo(rootTreeValue.Name));
        Assert.That(parentAuditEntry.AuditLogId, Is.EqualTo(auditDetail.Id));
        Assert.That(parentAuditEntry.PrimaryKey, Is.EqualTo($"{{\"Id\":{rootTreeValue.Id}}}"));
        Assert.That(parentAuditEntry.IsPrimary, Is.False);

        Assert.That(testDBContext.AuditDrillHierarchies.Count(), Is.EqualTo(1));
        var auditDrillHierarchies = testDBContext.AuditDrillHierarchies.ToList();
        Assert.That(auditDrillHierarchies[0].ChildAuditDrillDownEntityId, Is.EqualTo(1));
        Assert.That(auditDrillHierarchies[0].ParentAuditDrillDownEntityId, Is.EqualTo(2));
    }

    [TestCase(true)] //Soft delete
    [TestCase(false)] //Undelete
    public async Task AddRow_TableHasTwoParentLevels_AuditForSoftDeleteOrUndeleteRecorded(bool deleteRow)
    {
        //Arrange
        var auditUserDetails = new AuditUserDetails
        {
            UserId = 1,
            UserDisplayName = "Testy McTester",
            Comment = "Test comment"
        };

        var rootTreeValue = new DeletableTreeValue
        {
            Id = 300,
            Name = "Root Level item",
            Deleted = !deleteRow
        };

        var level1TreeValue = new DeletableTreeValue
        {
            Id = 3000,
            Name = "Level 1 item",
            Deleted = !deleteRow,
            ParentId = rootTreeValue.Id
        };

        var leafTreeValue = new DeletableTreeValue
        {
            Id = 30000,
            Name = "Leaf Level item",
            Deleted = !deleteRow,
            ParentId = level1TreeValue.Id
        };

        testDBContext.DeletableTreeValues.Add(rootTreeValue);
        testDBContext.DeletableTreeValues.Add(level1TreeValue);
        testDBContext.DeletableTreeValues.Add(leafTreeValue);
        await testDBContext.NoAuditSaveChangesAsync();

        //Act
        var itemToEdit = testDBContext.DeletableTreeValues.Single(x => x.Id == leafTreeValue.Id);
        itemToEdit.Deleted = deleteRow;
        await testDBContext.SaveChangesAsync(auditUserDetails);

        //Assert
        Assert.That(testDBContext.AuditDetails.Count(), Is.EqualTo(1));
        var auditDetail = testDBContext.AuditDetails.First();
        Assert.That(auditDetail.UserDisplayName, Is.EqualTo(auditUserDetails.UserDisplayName));
        Assert.That(auditDetail.UserId, Is.EqualTo(auditUserDetails.UserId));
        Assert.That(auditDetail.Comment, Is.EqualTo(auditUserDetails.Comment));

        Assert.That(auditDetail.Id, Is.EqualTo(1));

        Assert.That(auditDetail.Type, Is.EqualTo(deleteRow ? AuditType.Delete.ToString() : AuditType.Restore.ToString()));

        Assert.That(testDBContext.DeletableTreeValues.Count(), Is.EqualTo(3));

        Assert.That(testDBContext.AuditEntries.Count(), Is.EqualTo(3));

        var auditEntries = testDBContext.AuditEntries.ToList();
        var childAuditEntry = auditEntries[0];
        Assert.That(childAuditEntry.Id, Is.EqualTo(1));
        Assert.That(childAuditEntry.EntityName, Is.EqualTo(typeof(DeletableTreeValue).FullName));
        Assert.That(childAuditEntry.DisplayName, Is.EqualTo(leafTreeValue.Name));
        Assert.That(childAuditEntry.AuditLogId, Is.EqualTo(auditDetail.Id));
        Assert.That(childAuditEntry.PrimaryKey, Is.EqualTo($"{{\"Id\":{leafTreeValue.Id}}}"));
        Assert.That(childAuditEntry.IsPrimary, Is.True);

        var level1AuditEntry = auditEntries[1];
        Assert.That(level1AuditEntry.Id, Is.EqualTo(2));
        Assert.That(level1AuditEntry.EntityName, Is.EqualTo(typeof(DeletableTreeValue).FullName));
        Assert.That(level1AuditEntry.DisplayName, Is.EqualTo(level1TreeValue.Name));
        Assert.That(level1AuditEntry.AuditLogId, Is.EqualTo(auditDetail.Id));
        Assert.That(level1AuditEntry.PrimaryKey, Is.EqualTo($"{{\"Id\":{level1TreeValue.Id}}}"));
        Assert.That(level1AuditEntry.IsPrimary, Is.False);

        var parentAuditEntry = auditEntries[2];
        Assert.That(parentAuditEntry.Id, Is.EqualTo(3));
        Assert.That(parentAuditEntry.EntityName, Is.EqualTo(typeof(DeletableTreeValue).FullName));
        Assert.That(parentAuditEntry.DisplayName, Is.EqualTo(rootTreeValue.Name));
        Assert.That(parentAuditEntry.AuditLogId, Is.EqualTo(auditDetail.Id));
        Assert.That(parentAuditEntry.PrimaryKey, Is.EqualTo($"{{\"Id\":{rootTreeValue.Id}}}"));
        Assert.That(parentAuditEntry.IsPrimary, Is.False);

        Assert.That(testDBContext.AuditDrillHierarchies.Count(), Is.EqualTo(2));
        var auditDrillHierarchies = testDBContext.AuditDrillHierarchies.ToList();
        Assert.That(auditDrillHierarchies[0].ChildAuditDrillDownEntityId, Is.EqualTo(1));
        Assert.That(auditDrillHierarchies[0].ParentAuditDrillDownEntityId, Is.EqualTo(2));

        Assert.That(auditDrillHierarchies[1].ChildAuditDrillDownEntityId, Is.EqualTo(2));
        Assert.That(auditDrillHierarchies[1].ParentAuditDrillDownEntityId, Is.EqualTo(3));
    }

    [TestCase(true)] //Soft delete
    [TestCase(false)] //Undelete
    public async Task AddRow_TableHasThreeParentLevels_AuditForSoftDeleteOrUndeleteRecorded(bool deleteRow)
    {
        //Arrange
        var auditUserDetails = new AuditUserDetails
        {
            UserId = 1,
            UserDisplayName = "Testy McTester",
            Comment = "Test comment"
        };

        var rootTreeValue = new DeletableTreeValue
        {
            Id = 30,
            Name = "Root Level item",
            Deleted = !deleteRow
        };

        var level1TreeValue = new DeletableTreeValue
        {
            Id = 300,
            Name = "Level 1 item",
            Deleted = !deleteRow,
            ParentId = rootTreeValue.Id
        };

        var level2TreeValue = new DeletableTreeValue
        {
            Id = 3000,
            Name = "Level 2 item",
            Deleted = !deleteRow,
            ParentId = level1TreeValue.Id
        };

        var leafTreeValue = new DeletableTreeValue
        {
            Id = 30000,
            Name = "Leaf Level item",
            Deleted = !deleteRow,
            ParentId = level2TreeValue.Id
        };

        testDBContext.DeletableTreeValues.Add(rootTreeValue);
        await testDBContext.NoAuditSaveChangesAsync();

        testDBContext.DeletableTreeValues.Add(level1TreeValue);
        await testDBContext.NoAuditSaveChangesAsync();

        testDBContext.DeletableTreeValues.Add(level2TreeValue);
        await testDBContext.NoAuditSaveChangesAsync();

        testDBContext.DeletableTreeValues.Add(leafTreeValue);
        await testDBContext.NoAuditSaveChangesAsync();

        //Act
        var itemToEdit = testDBContext.DeletableTreeValues.Single(x => x.Id == leafTreeValue.Id);
        itemToEdit.Deleted = deleteRow;
        await testDBContext.SaveChangesAsync(auditUserDetails);

        //Assert
        Assert.That(testDBContext.AuditDetails.Count(), Is.EqualTo(1));
        var auditDetail = testDBContext.AuditDetails.First();
        Assert.That(auditDetail.UserDisplayName, Is.EqualTo(auditUserDetails.UserDisplayName));
        Assert.That(auditDetail.UserId, Is.EqualTo(auditUserDetails.UserId));
        Assert.That(auditDetail.Comment, Is.EqualTo(auditUserDetails.Comment));

        Assert.That(auditDetail.Id, Is.EqualTo(1));

        Assert.That(auditDetail.Type, Is.EqualTo(deleteRow ? AuditType.Delete.ToString() : AuditType.Restore.ToString()));

        Assert.That(testDBContext.DeletableTreeValues.Count(), Is.EqualTo(4));

        Assert.That(testDBContext.AuditEntries.Count(), Is.EqualTo(4));

        var auditEntries = testDBContext.AuditEntries.ToList();
        var childAuditEntry = auditEntries[0];
        Assert.That(childAuditEntry.Id, Is.EqualTo(1));
        Assert.That(childAuditEntry.EntityName, Is.EqualTo(typeof(DeletableTreeValue).FullName));
        Assert.That(childAuditEntry.DisplayName, Is.EqualTo(leafTreeValue.Name));
        Assert.That(childAuditEntry.AuditLogId, Is.EqualTo(auditDetail.Id));
        Assert.That(childAuditEntry.PrimaryKey, Is.EqualTo($"{{\"Id\":{leafTreeValue.Id}}}"));
        Assert.That(childAuditEntry.IsPrimary, Is.True);

        var level1AuditEntry = auditEntries[1];
        Assert.That(level1AuditEntry.Id, Is.EqualTo(2));
        Assert.That(level1AuditEntry.EntityName, Is.EqualTo(typeof(DeletableTreeValue).FullName));
        Assert.That(level1AuditEntry.DisplayName, Is.EqualTo(level2TreeValue.Name));
        Assert.That(level1AuditEntry.AuditLogId, Is.EqualTo(auditDetail.Id));
        Assert.That(level1AuditEntry.PrimaryKey, Is.EqualTo($"{{\"Id\":{level2TreeValue.Id}}}"));
        Assert.That(level1AuditEntry.IsPrimary, Is.False);

        var level2AuditEntry = auditEntries[2];
        Assert.That(level2AuditEntry.Id, Is.EqualTo(3));
        Assert.That(level2AuditEntry.EntityName, Is.EqualTo(typeof(DeletableTreeValue).FullName));
        Assert.That(level2AuditEntry.DisplayName, Is.EqualTo(level1TreeValue.Name));
        Assert.That(level2AuditEntry.AuditLogId, Is.EqualTo(auditDetail.Id));
        Assert.That(level2AuditEntry.PrimaryKey, Is.EqualTo($"{{\"Id\":{level1TreeValue.Id}}}"));
        Assert.That(level2AuditEntry.IsPrimary, Is.False);

        var parentAuditEntry = auditEntries[3];
        Assert.That(parentAuditEntry.Id, Is.EqualTo(4));
        Assert.That(parentAuditEntry.EntityName, Is.EqualTo(typeof(DeletableTreeValue).FullName));
        Assert.That(parentAuditEntry.DisplayName, Is.EqualTo(rootTreeValue.Name));
        Assert.That(parentAuditEntry.AuditLogId, Is.EqualTo(auditDetail.Id));
        Assert.That(parentAuditEntry.PrimaryKey, Is.EqualTo($"{{\"Id\":{rootTreeValue.Id}}}"));
        Assert.That(parentAuditEntry.IsPrimary, Is.False);

        Assert.That(testDBContext.AuditDrillHierarchies.Count(), Is.EqualTo(3));
        var auditDrillHierarchies = testDBContext.AuditDrillHierarchies.ToList();
        Assert.That(auditDrillHierarchies[0].ChildAuditDrillDownEntityId, Is.EqualTo(1));
        Assert.That(auditDrillHierarchies[0].ParentAuditDrillDownEntityId, Is.EqualTo(2));

        Assert.That(auditDrillHierarchies[1].ChildAuditDrillDownEntityId, Is.EqualTo(2));
        Assert.That(auditDrillHierarchies[1].ParentAuditDrillDownEntityId, Is.EqualTo(3));

        Assert.That(auditDrillHierarchies[2].ChildAuditDrillDownEntityId, Is.EqualTo(3));
        Assert.That(auditDrillHierarchies[2].ParentAuditDrillDownEntityId, Is.EqualTo(4));
    }
}