﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using Abp.Dapper.Repositories;
using Abp.Domain.Repositories;
using Abp.Domain.Uow;
using Abp.EntityFrameworkCore.Dapper.Tests.Domain;

using Microsoft.EntityFrameworkCore;

using Shouldly;

using Xunit;

namespace Abp.EntityFrameworkCore.Dapper.Tests.Tests
{
    public class Repository_Tests : AbpEfCoreDapperTestApplicationBase
    {
        private readonly IDapperRepository<Blog> _blogDapperRepository;
        private readonly IRepository<Blog> _blogRepository;
        private readonly IDapperRepository<Post, Guid> _postDapperRepository;
        private readonly IRepository<Post, Guid> _postRepository;
        private readonly IRepository<Comment, long> _commentRepository;
        private readonly IDapperRepository<Comment, long> _commentDapperRepository;
        private readonly IUnitOfWorkManager _uowManager;

        public Repository_Tests()
        {
            _uowManager = Resolve<IUnitOfWorkManager>();
            _blogRepository = Resolve<IRepository<Blog>>();
            _postRepository = Resolve<IRepository<Post, Guid>>();
            _blogDapperRepository = Resolve<IDapperRepository<Blog>>();
            _postDapperRepository = Resolve<IDapperRepository<Post, Guid>>();
            _commentRepository = Resolve<IRepository<Comment, long>>();
            _commentDapperRepository = Resolve<IDapperRepository<Comment, long>>();
        }

        [Fact]
        public void Should_Get_Initial_Blogs()
        {
            //Act
            List<Blog> blogs = _blogRepository.GetAllList();
            IEnumerable<Blog> blogsFromDapper = _blogDapperRepository.GetAll();

            //Assert
            blogs.Count.ShouldBeGreaterThan(0);
            blogsFromDapper.Count().ShouldBeGreaterThan(0);
        }

        [Fact]
        public async Task Should_Automatically_Save_Changes_On_Uow()
        {
            int blog1Id;
            int blog2Id;

            //Act

            using (IUnitOfWorkCompleteHandle uow = _uowManager.Begin())
            {
                Blog blog1 = await _blogRepository.SingleAsync(b => b.Name == "test-blog-1");
                blog1Id = blog1.Id;

                blog1.Name = "test-blog-1-updated";

                await _blogDapperRepository.InsertAsync(new Blog("test-blog-2", "www"));

                Blog blog2 = _blogRepository.Single(x => x.Name == "test-blog-2");
                blog2Id = blog2.Id;

                blog2.Name = "test-blog-2-updated";

                _blogDapperRepository.Update(blog2);

                await uow.CompleteAsync();
            }

            //Assert

            await UsingDbContextAsync(async context =>
            {
                Blog blog1 = await context.Blogs.SingleAsync(b => b.Id == blog1Id);
                blog1.Name.ShouldBe("test-blog-1-updated");

                Blog blog2 = await context.Blogs.SingleAsync(b => b.Id == blog2Id);
                blog2.Name.ShouldBe("test-blog-2-updated");
            });
        }

        [Fact]
        public async Task Should_automatically_save_changes_on_uow_completed_with_dapper()
        {
            int blog1Id;

            //Act
            using (IUnitOfWorkCompleteHandle uow = _uowManager.Begin())
            {
                Blog blog1 = await _blogDapperRepository.SingleAsync(b => b.Name == "test-blog-1");
                blog1Id = blog1.Id;

                blog1.Name = "test-blog-1-updated";
                _blogDapperRepository.Update(blog1);

                await uow.CompleteAsync();
            }

            //Assert

            await UsingDbContextAsync(async context =>
            {
                Blog blog1 = await context.Blogs.SingleAsync(b => b.Id == blog1Id);
                blog1.Name.ShouldBe("test-blog-1-updated");
            });
        }

        [Fact]
        public async Task Should_Not_Include_Navigation_Properties_If_Not_Requested()
        {
            //EF Core does not support lazy loading yet, so navigation properties will not be loaded if not included

            using (IUnitOfWorkCompleteHandle uow = _uowManager.Begin())
            {
                Post post = await _postRepository.GetAll().FirstAsync();

                post.Blog.ShouldBeNull();

                await uow.CompleteAsync();
            }
        }

        [Fact]
        public async Task Should_Include_Navigation_Properties_If_Requested()
        {
            using (IUnitOfWorkCompleteHandle uow = _uowManager.Begin())
            {
                Post post = await _postRepository.GetAllIncluding(p => p.Blog).FirstAsync();

                post.Blog.ShouldNotBeNull();
                post.Blog.Name.ShouldBe("test-blog-1");

                await uow.CompleteAsync();
            }
        }

        [Fact]
        public async Task Should_Insert_New_Entity()
        {
            using (IUnitOfWorkCompleteHandle uow = _uowManager.Begin())
            {
                var blog = new Blog("blog2", "http://myblog2.com");
                blog.IsTransient().ShouldBeTrue();
                await _blogRepository.InsertAsync(blog);
                await uow.CompleteAsync();
                blog.IsTransient().ShouldBeFalse();
            }
        }

        [Fact]
        public async Task Should_Insert_New_Entity_with_dapper()
        {
            using (IUnitOfWorkCompleteHandle uow = _uowManager.Begin())
            {
                var blog = new Blog("blog2", "http://myblog2.com");
                blog.IsTransient().ShouldBeTrue();
                await _blogDapperRepository.InsertAsync(blog);
                await uow.CompleteAsync();
                blog.IsTransient().ShouldBeFalse();
            }
        }

        [Fact]
        public async Task Should_Insert_New_Entity_With_Guid_Id()
        {
            using (IUnitOfWorkCompleteHandle uow = _uowManager.Begin())
            {
                Blog blog1 = await _blogRepository.GetAsync(1);
                var post = new Post(blog1, "a test title", "a test body");
                post.IsTransient().ShouldBeTrue();
                await _postRepository.InsertAsync(post);
                await uow.CompleteAsync();
                post.IsTransient().ShouldBeFalse();
            }
        }

        [Fact]
        public async Task Should_Insert_New_Entity_With_Guid_Id_with_dapper()
        {
            using (IUnitOfWorkCompleteHandle uow = _uowManager.Begin())
            {
                Blog blog1 = await _blogRepository.GetAsync(1);
                var post = new Post(blog1.Id, "a test title", "a test body");
                post.IsTransient().ShouldBeTrue();
                await _postDapperRepository.InsertAsync(post);
                await uow.CompleteAsync();
                post.IsTransient().ShouldBeFalse();
            }
        }

        [Fact]
        public void Dapper_and_EfCore_should_work_under_same_unitofwork()
        {
            using (IUnitOfWorkCompleteHandle uow = Resolve<IUnitOfWorkManager>().Begin())
            {
                int blogId = _blogDapperRepository.InsertAndGetId(new Blog("Oguzhan_Same_Uow", "www"));

                Blog blog = _blogRepository.Get(blogId);

                blog.ShouldNotBeNull();

                uow.Complete();
            }
        }

        [Fact]
        public async Task execute_method_for_void_sqls_should_work()
        {
            int blogId = _blogDapperRepository.InsertAndGetId(new Blog("Oguzhan_Blog", "wwww.aspnetboilerplate.com"));

            await _blogDapperRepository.ExecuteAsync("Update Blogs Set Name = @name where Id =@id", new { id = blogId, name = "Oguzhan_New_Blog" });

            _blogDapperRepository.Get(blogId).Name.ShouldBe("Oguzhan_New_Blog");
            _blogRepository.Get(blogId).Name.ShouldBe("Oguzhan_New_Blog");
        }

        [Fact]
        public void querying_with_TEntity_TPrimaryKey_should_work_on_dapper_repositories()
        {
            _commentRepository.Insert(new Comment("hey!"));

            List<Comment> comments = _commentDapperRepository.Query("select * from Comments").ToList();
            List<Comment> comments2 = _commentDapperRepository.Query<Comment>("select * from Comments").ToList();

            comments2.Count.ShouldBe(1);
            comments.Count.ShouldBe(1);
        }
    }
}
