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 EditRowUnitTests : AuditEngineCoreTestBase
{
    [Test]
    public async Task UpdateRow_TableNotAudited_NoAuditRecordCreated()
    {
        //Arrange
        var auditUserDetails = new AuditUserDetails
        {
            UserId = 1,
            UserDisplayName = "Testy McTester",
            Comment = "Test comment"
        };

        var leafTreeValue = new GeneralComment
        {
            Id = 30000,
            Comment = "Original Comment",
        };

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

        //Act
        var itemToEdit = testDBContext.GeneralComments.Single(x => x.Id == leafTreeValue.Id);
        itemToEdit.Comment = "Modified Comment";
        await testDBContext.SaveChangesAsync(auditUserDetails);

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

    [Test]
    public async Task UpdateRow_TableAudited_AuditRecordIsCreated()
    {
        //Arrange
        const string unmodifiedName = "Leaf Level item";
        const string modifiedName = "Modified Name";

        var auditUserDetails = new AuditUserDetails
        {
            UserId = 1,
            UserDisplayName = "Testy McTester",
            Comment = "Test comment"
        };

        var leafTreeValue = new DeletableTreeValue
        {
            Id = 30000,
            Name = unmodifiedName,
            Test = "Test Value"
        };

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

        //Act
        var itemToEdit = testDBContext.DeletableTreeValues.Single(x => x.Id == leafTreeValue.Id);
        itemToEdit.Name = modifiedName;

        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(AuditType.Update.ToString()));
        Assert.That(auditDetail.Fields, Is.EqualTo($"{{\"Name\":{{\"OldValue\":\"{unmodifiedName}\",\"NewValue\":\"{modifiedName}\"}}}}"));


        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(modifiedName));
        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));
    }

    [Test]
    public async Task UpdateRow_MultipleFieldsEdited_AuditRecordIsCreated()
    {
        //Arrange
        const string unmodifiedName = "Leaf Level item";
        const string modifiedName = "Modified Name";

        const string unmodifiedTestValue = "Test Value";
        const string modifiedTestValue = "Altered Test Value";

        var auditUserDetails = new AuditUserDetails
        {
            UserId = 1,
            UserDisplayName = "Testy McTester",
            Comment = "Test comment"
        };

        var leafTreeValue = new DeletableTreeValue
        {
            Id = 30000,
            Name = unmodifiedName,
            Test = unmodifiedTestValue
        };

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

        //Act
        var itemToEdit = testDBContext.DeletableTreeValues.Single(x => x.Id == leafTreeValue.Id);
        itemToEdit.Name = modifiedName;
        itemToEdit.Test = modifiedTestValue;

        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(AuditType.Update.ToString()));
        Assert.That(auditDetail.Fields,
            Is.EqualTo($"{{\"Name\":{{\"OldValue\":\"{unmodifiedName}\",\"NewValue\":\"{modifiedName}\"}},\"Test\":{{\"OldValue\":\"{unmodifiedTestValue}\",\"NewValue\":\"{modifiedTestValue}\"}}}}"));

        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(modifiedName));
        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));
    }

    [Test]
    public async Task UpdateRow_WhenUpdatingSecretValue_AuditRecordIsMarkedAsRedacted()
    {
        //Arrange
        const string originalSecret = "I've forgotten";
        const string modifiedSecret = "I'm not telling you";

        var auditUserDetails = new AuditUserDetails
        {
            UserId = 1,
            UserDisplayName = "Testy McTester",
            Comment = "Test comment"
        };

        var leafTreeValue = new SecretValue
        {
            Id = 30000,
            Name = "Test secret",
            Secret = originalSecret
        };

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

        //Act
        var itemToEdit = testDBContext.SecretValues.Single(x => x.Id == leafTreeValue.Id);
        itemToEdit.Secret = modifiedSecret;
        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(AuditType.Update.ToString()));
        Assert.That(auditDetail.Fields, Is.EqualTo($"{{\"Secret\":{{\"OldValue\":\"<Redacted>\",\"NewValue\":\"<Redacted>\"}}}}"));

        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(SecretValue).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));
    }

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

        testDBContext.ParentValues.Add(new ParentValue
        {
            Id = 10,
            Name = "Parent 10",
        });

        testDBContext.ParentValues.Add(new ParentValue
        {
            Id = 20,
            Name = "Parent 20",
        });

        testDBContext.ChildValues.Add(new ChildValue
        {
            Id = 1,
            Name = "ChildValue",
            ParentId = null
        });
        await testDBContext.NoAuditSaveChangesAsync();

        //Act

        var childValue = testDBContext.ChildValues.Single(x => x.Id == 1);
        childValue.ParentId = 20;
        await testDBContext.SaveChangesAsync(auditUserDetails);

        //Assert
        var auditEntries = testDBContext.AuditEntries.Where( x => x.IsPrimary == true).ToList();
        Assert.That(auditEntries.Count, Is.EqualTo(1));
        var entry = auditEntries.First();
        Assert.That(entry.AuditLog.Fields, Is.EqualTo("{\"ParentId\":{\"NewValue\":20,\"NewDisplayName\":\"Parent 20\"}}"));
    }

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

        testDBContext.ParentValues.Add(new ParentValue
        {
            Id = 10,
            Name = "Parent 10",
        });

        testDBContext.ParentValues.Add(new ParentValue
        {
            Id = 20,
            Name = "Parent 20",
        });

        testDBContext.ChildValues.Add(new ChildValue
        {
            Id = 1,
            Name = "ChildValue",
            ParentId = 10
        });
        await testDBContext.NoAuditSaveChangesAsync();

        //Act

        var childValue = testDBContext.ChildValues.Single(x => x.Id == 1);
        childValue.ParentId = 20;
        await testDBContext.SaveChangesAsync(auditUserDetails);

        //Assert
        var auditEntries = testDBContext.AuditEntries.Where(x => x.IsPrimary == true).ToList();
        Assert.That(auditEntries.Count, Is.EqualTo(1));
        var entry = auditEntries.First();
        Assert.That(entry.AuditLog.Fields, Is.EqualTo("{\"ParentId\":{\"OldValue\":10,\"OldDisplayName\":\"Parent 10\",\"NewValue\":20,\"NewDisplayName\":\"Parent 20\"}}"));
    }

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

        testDBContext.ParentValues.Add(new ParentValue
        {
            Id = 10,
            Name = "Parent 10",
        });

        testDBContext.ParentValues.Add(new ParentValue
        {
            Id = 20,
            Name = "Parent 20",
        });

        testDBContext.ChildValues.Add(new ChildValue
        {
            Id = 1,
            Name = "ChildValue",
            ParentId = 10
        });
        await testDBContext.NoAuditSaveChangesAsync();

        //Act

        var childValue = testDBContext.ChildValues.Single(x => x.Id == 1);
        childValue.ParentId = null;
        await testDBContext.SaveChangesAsync(auditUserDetails);

        //Assert
        var auditEntries = testDBContext.AuditEntries.Where(x => x.IsPrimary == true).ToList();
        Assert.That(auditEntries.Count, Is.EqualTo(1));
        var entry = auditEntries.First();
        Assert.That(entry.AuditLog.Fields, Is.EqualTo("{\"ParentId\":{\"OldValue\":10,\"OldDisplayName\":\"Parent 10\"}}"));
    }
}