﻿using Dapper;
using EzApp.Data.Core.DataModels;
using EzApp.Data.Sql.Core.SqlBuilder;
using EzApp.Data.Sql.Dapper.DbConnectionOperators;
using EzApp.Data.Sql.Dapper.Parameters;
using EzApp.Data.Sql.Dapper.Repositories;
using EzApp.Logging.Core;
using EzApp.Mapping.Core;
using EzApp.Tests.Core.TestClasses;
using Moq;
using NUnit.Framework;
using System;
using System.Data;
using System.Linq;

namespace EzApp.Tests.UnitTests.Data.Sql.Dapper.RepositoryTests.EzDapperCrudRepositoryTests
{
    [TestFixture]
    public class EzDapperCrudRepository_Update
    {
        [Test]
        public void PerformsObjectMapping___GivenRepository__WithMapper__WhenUpdateIsCalled__ShouldMapCreateDtoToUpdateDataModel_Once()
        {
            var mockMapper = new Mock<IEzMapper>();

            var mockLogger = new Mock<IEzLogger>();

            var mockSqlBuilder = new Mock<IEzSqlBuilderService>();

            var mockDapperParametersBuilder = new Mock<IEzDapperParametersBuilder>();

            var mockDbConnectionOperator = new Mock<IEzDapperDbConnectionOperator<TestEntity, int, TestEntitySelectDataModel, TestEntityFilters>>();
            
            var sut = new EzDapperCrudRepository<TestEntity, int, TestEntitySelectDataModel, TestEntityInsertDataModel, TestEntityCreateDto, TestEntityUpdateDataModel, TestEntityUpdateDto, TestEntityFilters>(
                mockMapper.Object,
                mockLogger.Object,
                mockSqlBuilder.Object,
                mockDapperParametersBuilder.Object,
                mockDbConnectionOperator.Object);

            var entityUpdateDto = new TestEntityUpdateDto();

            sut.Update(entityUpdateDto);

            mockMapper.Verify(x => x.Map<TestEntityUpdateDto, TestEntityUpdateDataModel>(entityUpdateDto), Times.Once);
        }

        [Test]
        public void GetsParametersFromCorrectObject___GivenRepository__WithMapper__AndWithParametersBuilder__WhenUpdateIsCalled__ShouldBuildParametersFromMappingResult_Once()
        {
            var mockMapper = new Mock<IEzMapper>();

            var mockLogger = new Mock<IEzLogger>();

            var mockSqlBuilder = new Mock<IEzSqlBuilderService>();

            var mockDapperParametersBuilder = new Mock<IEzDapperParametersBuilder>();

            var mockDbConnectionOperator = new Mock<IEzDapperDbConnectionOperator<TestEntity, int, TestEntitySelectDataModel, TestEntityFilters>>();

            var sut = new EzDapperCrudRepository<TestEntity, int, TestEntitySelectDataModel, TestEntityInsertDataModel, TestEntityCreateDto, TestEntityUpdateDataModel, TestEntityUpdateDto, TestEntityFilters>(
                mockMapper.Object,
                mockLogger.Object,
                mockSqlBuilder.Object,
                mockDapperParametersBuilder.Object,
                mockDbConnectionOperator.Object);

            var entityUpdateDataModel = new TestEntityUpdateDataModel();
            mockMapper.Setup(x => x.Map<TestEntityUpdateDto, TestEntityUpdateDataModel>(It.IsAny<TestEntityUpdateDto>())).Returns(entityUpdateDataModel);

            sut.Update(It.IsAny<TestEntityUpdateDto>());

            mockDapperParametersBuilder.Verify(x => x.BuildParameters(entityUpdateDataModel), Times.Once);
        }

        [Test]
        public void BuildsSqlFromCorrectObject___GivenRepository__WithMapper__AndWithSqlBuilderService__WhenUpdateIsCalled__ShouldBuildSqlFromMappingResult_Once()
        {
            var mockMapper = new Mock<IEzMapper>();

            var mockLogger = new Mock<IEzLogger>();

            var mockSqlBuilder = new Mock<IEzSqlBuilderService>();

            var mockDapperParametersBuilder = new Mock<IEzDapperParametersBuilder>();

            var mockDbConnectionOperator = new Mock<IEzDapperDbConnectionOperator<TestEntity, int, TestEntitySelectDataModel, TestEntityFilters>>();

            var sut = new EzDapperCrudRepository<TestEntity, int, TestEntitySelectDataModel, TestEntityInsertDataModel, TestEntityCreateDto, TestEntityUpdateDataModel, TestEntityUpdateDto, TestEntityFilters>(
                mockMapper.Object,
                mockLogger.Object,
                mockSqlBuilder.Object,
                mockDapperParametersBuilder.Object,
                mockDbConnectionOperator.Object);

            var entityUpdateDataModel = new TestEntityUpdateDataModel();
            mockMapper.Setup(x => x.Map<TestEntityUpdateDto, TestEntityUpdateDataModel>(It.IsAny<TestEntityUpdateDto>())).Returns(entityUpdateDataModel);

            sut.Update(It.IsAny<TestEntityUpdateDto>());
            
            mockSqlBuilder.Verify(x => x.BuildUpdateSql(entityUpdateDataModel), Times.Once);
        }

        [Test]
        public void PassesCorrectSqlAndParametersToOperation___GivenRepository__WithParametersBuilder__AndDbConnectionOperator__WhenUpdateIsCalled__ShouldPassCorrectSqlAndParametersToDbConnectionOperation_Once()
        {
            var mockMapper = new Mock<IEzMapper>();

            var mockLogger = new Mock<IEzLogger>();

            var mockSqlBuilder = new Mock<IEzSqlBuilderService>();

            var mockDapperParametersBuilder = new Mock<IEzDapperParametersBuilder>();

            var mockDbConnectionOperator = new Mock<IEzDapperDbConnectionOperator<TestEntity, int, TestEntitySelectDataModel, TestEntityFilters>>();

            var sut = new EzDapperCrudRepository<TestEntity, int, TestEntitySelectDataModel, TestEntityInsertDataModel, TestEntityCreateDto, TestEntityUpdateDataModel, TestEntityUpdateDto, TestEntityFilters>(
                mockMapper.Object,
                mockLogger.Object,
                mockSqlBuilder.Object,
                mockDapperParametersBuilder.Object,
                mockDbConnectionOperator.Object);

            var sql = "the sql";
            mockSqlBuilder.Setup(x => x.BuildUpdateSql(It.IsAny<IEzDataModel>())).Returns(sql);

            var parameters = new { };
            mockDapperParametersBuilder.Setup(x => x.BuildParameters(It.IsAny<object>())).Returns(parameters);
            
            var func = new Func<IDbConnection, TestEntity>(x => x.Query<TestEntity>(sql, parameters).FirstOrDefault());

            sut.Update(It.IsAny<TestEntityUpdateDto>());

            mockDbConnectionOperator.Verify(x => x.PerformUpdate(sql, parameters), Times.Once);
        }

        [Test]
        public void MapsOperationResult___GivenRepository__WithDbConnectionOperator__AndMapper__WhenUpdateIsCalled__ShouldMapOperationResultToEntity_Once()
        {
            var mockMapper = new Mock<IEzMapper>();

            var mockLogger = new Mock<IEzLogger>();

            var mockSqlBuilder = new Mock<IEzSqlBuilderService>();

            var mockDapperParametersBuilder = new Mock<IEzDapperParametersBuilder>();

            var mockDbConnectionOperator = new Mock<IEzDapperDbConnectionOperator<TestEntity, int, TestEntitySelectDataModel, TestEntityFilters>>();

            var sut = new EzDapperCrudRepository<TestEntity, int, TestEntitySelectDataModel, TestEntityInsertDataModel, TestEntityCreateDto, TestEntityUpdateDataModel, TestEntityUpdateDto, TestEntityFilters>(
                mockMapper.Object,
                mockLogger.Object,
                mockSqlBuilder.Object,
                mockDapperParametersBuilder.Object,
                mockDbConnectionOperator.Object);

            var selectDataModel = new TestEntitySelectDataModel();
            mockDbConnectionOperator.Setup(x => x.PerformUpdate(It.IsAny<string>(), It.IsAny<object>())).Returns(selectDataModel);
            
            sut.Update(It.IsAny<TestEntityUpdateDto>());
            
            mockMapper.Verify(x => x.Map<TestEntitySelectDataModel, TestEntity>(selectDataModel), Times.Once);
        }

        [Test]
        public void ReturnsMappedEntity___GivenRepository__WithMapper__WhenUpdateIsCalled__ShouldReturnEntityMappedFromSelectDataModel()
        {
            var mockMapper = new Mock<IEzMapper>();

            var mockLogger = new Mock<IEzLogger>();

            var mockSqlBuilder = new Mock<IEzSqlBuilderService>();

            var mockDapperParametersBuilder = new Mock<IEzDapperParametersBuilder>();

            var mockDbConnectionOperator = new Mock<IEzDapperDbConnectionOperator<TestEntity, int, TestEntitySelectDataModel, TestEntityFilters>>();

            var sut = new EzDapperCrudRepository<TestEntity, int, TestEntitySelectDataModel, TestEntityInsertDataModel, TestEntityCreateDto, TestEntityUpdateDataModel, TestEntityUpdateDto, TestEntityFilters>(
                mockMapper.Object,
                mockLogger.Object,
                mockSqlBuilder.Object,
                mockDapperParametersBuilder.Object,
                mockDbConnectionOperator.Object);

            var entity = new TestEntity();
            mockMapper.Setup(x => x.Map<TestEntitySelectDataModel, TestEntity>(It.IsAny<TestEntitySelectDataModel>())).Returns(entity);

            var result = sut.Update(It.IsAny<TestEntityUpdateDto>());
            
            Assert.That(result == entity);
        }
    }
}
