using e_suite.Database.Core.UnitTests.Helpers;
using e_suite.Database.Core.UnitTests.Helpers.Tables;
using eSuite.Core.Clock;
using Moq;
using NUnit.Framework;

namespace e_suite.Database.Core.UnitTests;

[TestFixture]
public class SunDatabaseEntityContextUnitTests
{
    protected TestDbContextFactory testDbContextFactory = null!;
    protected Mock<IClock> _clockMock = null!;
    public DateTimeOffset TestNow = DateTimeOffset.Now;

    protected TestDbContext TestDbContext = null!;

    [SetUp]
    public void Setup()
    {
        testDbContextFactory = new TestDbContextFactory();

        _clockMock = new Mock<IClock>();
        _clockMock.Setup(x => x.GetNow).Returns(() => TestNow);

        TestDbContext = testDbContextFactory.CreateContext(_clockMock.Object);
    }

    [Test]
    public void OnModelCreating_Executes_WithoutExceptions()
    {
        //Arrange
        //Act
        //  dealt with by the Setup method, as this ensures the database is created.

        //Assert
        Assert.That(TestDbContext.TableWithKey.Count(), Is.EqualTo(1));
    }

    [Test]
    public async Task TransActionAsyncAction_Executing_CodeRunsInATransaction()
    {
        //Arrange
        Guid? transactionId = null;


        //Act
        await TestDbContext.TransactionAsync(async () =>
        {
            transactionId = TestDbContext.Database.CurrentTransaction?.TransactionId;
            await TestDbContext.SaveChangesAsync();
        });

        //Assert
        Assert.That(transactionId, Is.Not.Null); //Was in a transaction when the code was executed
        Assert.That(TestDbContext.Database.CurrentTransaction?.TransactionId, Is.Null); //No longer in a transaction
    }

    [Test]
    public async Task TransActionAsyncAction_AlreadyInTransaction_DoesNotStartAnotherTransaction()
    {
        //Arrange
        Guid? transactionId = null;
        Guid? innerTransactionId = null;

        await TestDbContext.TransactionAsync(async () =>
        {
            transactionId = TestDbContext.Database.CurrentTransaction?.TransactionId;

            //Act
            await TestDbContext.TransactionAsync( async () =>
            {
                innerTransactionId = TestDbContext.Database.CurrentTransaction?.TransactionId;
                await TestDbContext.SaveChangesAsync();
            });
        });

        //Assert
        Assert.That(transactionId, Is.Not.Null); //Was in a transaction when the code was executed
        Assert.That(innerTransactionId, Is.EqualTo(transactionId)); //The inner transaction is the same as the outer transaction.  Transaction inception is not allowed.
    }

    [Test]
    public async Task TransActionAsyncAction_InsertedRecordWithNoException_TransactionCompletes()
    {
        //Arrange
        var newItem = new TableWithKey()
        {
            Id = 2,
            TotalTimeMS = 5000
        };

        Assert.That(TestDbContext.TableWithKey.Count(), Is.EqualTo(1));

        //Act
        await TestDbContext.TransactionAsync(async () =>
        {
            TestDbContext.TableWithKey.Add(newItem);
            await TestDbContext.SaveChangesAsync();
        });

        //Assert
        Assert.That(TestDbContext.TableWithKey.Count(), Is.EqualTo(2));
    }

    [Test]
    public async Task TransActionAsyncAction_InsertedRecordThenException_TransactionIsRolledback()
    {
        //Arrange
        var newItem = new TableWithKey()
        {
            Id = 2,
            TotalTimeMS = 5000
        };

        Assert.That(TestDbContext.TableWithKey.Count(), Is.EqualTo(1));

        //Act
        try
        {
            await TestDbContext.TransactionAsync(async () =>
            {
                TestDbContext.TableWithKey.Add(newItem);
                await TestDbContext.SaveChangesAsync();

                throw new Exception("Testing rollback");
            });
        }
        catch
        {
            //This was supposed to happen
        }

        //Assert
        Assert.That(TestDbContext.TableWithKey.Count(), Is.EqualTo(1));
    }

    [Test]
    public async Task TransActionAsyncFunc_Executing_CodeRunsInATransaction()
    {
        //Arrange
        Guid? transactionId = null;

        //Act
        var result = await TestDbContext.TransactionAsync(async () =>
        {
            transactionId = TestDbContext.Database.CurrentTransaction?.TransactionId;

            await TestDbContext.SaveChangesAsync();
            return true;
        });

        //Assert
        Assert.That(result, Is.True);
        Assert.That(transactionId, Is.Not.Null); //Was in a transaction when the code was executed
        Assert.That(TestDbContext.Database.CurrentTransaction?.TransactionId, Is.Null); //No longer in a transaction
    }

    [Test]
    public async Task TransActionAsyncFunc_InsertedRecordThenException_TransactionIsRolledback()
    {
        //Arrange
        var newItem = new TableWithKey()
        {
            Id = 2,
            TotalTimeMS = 5000
        };

        Assert.That(TestDbContext.TableWithKey.Count(), Is.EqualTo(1));

        //Act
        try
        {
            var result = await TestDbContext.TransactionAsync(async () =>
            {
                TestDbContext.TableWithKey.Add(newItem);
                await TestDbContext.SaveChangesAsync();

                throw new Exception("Testing rollback");

#pragma warning disable CS0162
                //This code will never be hit because this is a test, but it's needed so that the code will compile.
                return 1;
#pragma warning restore CS0162
            });
        }
        catch
        {
            //This was supposed to happen
        }

        //Assert
        Assert.That(TestDbContext.TableWithKey.Count(), Is.EqualTo(1));
    }
}