﻿using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using FluentAssertions;
using Moq;
using Xunit;

namespace OnionSeed.Data
{
	[SuppressMessage("AsyncUsage.CSharp.Naming", "UseAsyncSuffix:Use Async suffix", Justification = "Test methods don't need to end in 'Async'.")]
	public class AsyncUnitOfWorkExceptionHandlerTests
	{
		private readonly Mock<IAsyncUnitOfWork> _mockInner = new Mock<IAsyncUnitOfWork>(MockBehavior.Strict);

		[Theory]
		[InlineData(false, false)]
		[InlineData(false, true)]
		[InlineData(true, false)]
		public void Constructor_ShouldValidateParameters(bool includeInner, bool includeHandler)
		{
			// Arrange
			var inner = includeInner ? _mockInner.Object : null;
			var handler = includeHandler ? (Exception ex) => { } : (Action<Exception>)null;

			// Act
			Action action = () => new AsyncUnitOfWorkExceptionHandler<InvalidOperationException>(inner, handler);

			// Arrange
			action.Should().Throw<ArgumentNullException>();

			_mockInner.VerifyAll();
		}

		[Fact]
		public async Task CommitAsync_ShouldCallInner()
		{
			// Arrange
			InvalidOperationException exception = null;

			_mockInner
				.Setup(i => i.CommitAsync())
				.Returns(CompletedTask());

			var subject = new AsyncUnitOfWorkExceptionHandler<InvalidOperationException>(_mockInner.Object, ex => exception = ex);

			// Act
			await subject.CommitAsync().ConfigureAwait(false);

			// Assert
			exception.Should().BeNull();

			_mockInner.VerifyAll();
		}

		[Fact]
		public async Task CommitAsync_ShouldInvokeHandle_WhenInnerThrowsException()
		{
			// Arrange
			var entity = new FakeEntity<int>() { Id = 47 };
			InvalidOperationException exception = null;

			_mockInner
				.Setup(i => i.CommitAsync())
				.Returns(ExceptionTask(new InvalidOperationException()));

			var subject = new AsyncUnitOfWorkExceptionHandler<InvalidOperationException>(_mockInner.Object, ex => exception = ex);

			// Act
			await subject.CommitAsync().ConfigureAwait(false);

			// Assert
			exception.Should().NotBeNull();

			_mockInner.VerifyAll();
		}

		private static Task ExceptionTask(Exception ex)
		{
#if STATIC_TASKS
			return TaskHelpers.FromException(ex);
#else
			return Task.FromException(ex);
#endif
		}

		private static Task CompletedTask()
		{
#if STATIC_TASKS
			return TaskHelpers.CompletedTask;
#else
			return Task.CompletedTask;
#endif
		}
	}
}
