using AsbCloudApp.Data;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Repository;
using AsbCloudInfrastructure.Services.DrillingProgram;
using Mapster;
using Microsoft.Extensions.Configuration;
using Moq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace AsbCloudWebApi.Tests.ServicesTests
{
    public class DrillingProgramServiceTest
    {
        private const int idWell = 3001;
        private static readonly SimpleTimezone baseTimezone = new() { Hours = 5d };
        private readonly AsbCloudDbContext db;

        private static readonly List<Well> wells = new()
        {
            new Well { Id = 3001, Caption = "well 1", Timezone = baseTimezone },
            new Well { Id = 3002, Caption = "well 2", Timezone = baseTimezone },
        };

        private static readonly List<Company> companies = new() {
            new Company { Id = 3001, Caption = "company name", IdCompanyType = 2, },
            new Company { Id = 3002, Caption = "company name", IdCompanyType = 2, },
            new Company { Id = 3003, Caption = "company name", IdCompanyType = 2, },
        };

        private static readonly User publisher1 = new() { Id = 3001, IdCompany = 3001, Login = "user 1", Email = "aa@aa.aa", IdState = 2 };
        private static readonly User approver1 = new() { Id = 3002, IdCompany = 3001, Login = "user 2", Email = "aa@aa.aa", IdState = 2 };
        private static readonly User approver2 = new() { Id = 3003, IdCompany = 3002, Login = "user 3", Email = "aa@aa.aa", IdState = 2 };

        private static readonly List<User> users = new()
        {
            publisher1,
            approver1,
            approver2,
            new User { Id = 3004, IdCompany = 3001, Login = "wrong 1", Email = "",         IdState = 2 },
            new User { Id = 3005, IdCompany = 3003, Login = "wrong 2", Email = "aa@aa.aa", IdState = 2 },
        };

        private static readonly FileInfo file1001 = new()
        {
            Id = 3001,
            IdWell = idWell,
            IdCategory = 1001,
            IdAuthor = publisher1.Id,
            IsDeleted = false,
            Name = "file1.xlsx",
            Size = 1024,
            UploadDate = System.DateTimeOffset.UtcNow,
        };

        private static readonly FileInfo file1002 = new()
        {
            Id = 3002,
            IdWell = idWell,
            IdCategory = 1002,
            IdAuthor = publisher1.Id,
            IsDeleted = false,
            Name = "file2.xlsx",
            Size = 1024,
            UploadDate = System.DateTimeOffset.UtcNow,
        };

        private static readonly List<RelationCompanyWell> relationsCompanyWell = new()
        {
            new RelationCompanyWell { IdCompany = 3001, IdWell = 3001, },
            new RelationCompanyWell { IdCompany = 3002, IdWell = 3001, },
            new RelationCompanyWell { IdCompany = 3002, IdWell = 3002, },
            new RelationCompanyWell { IdCompany = 3003, IdWell = 3002, },
        };

        private readonly Mock<FileService> fileServiceMock;
        private readonly Mock<IUserRepository> userRepositoryMock;
        private readonly Mock<IWellService> wellServiceMock;
        private readonly Mock<IConfiguration> configurationMock;
        private readonly Mock<IBackgroundWorkerService> backgroundWorkerMock;
        private readonly Mock<IEmailService> emailService;

        public DrillingProgramServiceTest()
        {
            AsbCloudInfrastructure.DependencyInjection.MapsterSetup();

            db = TestHelpter.MakeRealTestContext();
            db.Wells.AddRange(wells);
            db.Companies.AddRange(companies);
            db.SaveChanges();
            db.Users.AddRange(users);
            db.RelationCompaniesWells.AddRange(relationsCompanyWell);
            db.SaveChanges();

            fileServiceMock = new Mock<FileService>();
            userRepositoryMock = new Mock<IUserRepository>();
            wellServiceMock = new Mock<IWellService>();
            configurationMock = new Mock<IConfiguration>();
            backgroundWorkerMock = new Mock<IBackgroundWorkerService>();
        }

        [Fact]
        public async Task GetAvailableUsers_returns_3_users()
        {
            var service = new DrillingProgramService(
                db,
                fileServiceMock.Object,
                userRepositoryMock.Object,
                wellServiceMock.Object,
                configurationMock.Object,
                backgroundWorkerMock.Object,
                emailService.Object);

            var users = await service.GetAvailableUsers(idWell, CancellationToken.None);

            Assert.Equal(3, users.Count());
        }

        [Fact]
        public async Task AddPartsAsync_returns_2()
        {
            var service = new DrillingProgramService(
                db,
                fileServiceMock.Object,
                userRepositoryMock.Object,
                wellServiceMock.Object,
                configurationMock.Object,
                backgroundWorkerMock.Object,
                emailService.Object);

            var result = await service.AddPartsAsync(idWell, new int[] { 1001, 1002 }, CancellationToken.None);

            Assert.Equal(2, result);
        }

        [Fact]
        public async Task RemovePartsAsync_returns_1()
        {
            db.DrillingProgramParts.Add(new DrillingProgramPart { IdFileCategory = 1005, IdWell = idWell });
            db.SaveChanges();
            var service = new DrillingProgramService(
                db,
                fileServiceMock.Object,
                userRepositoryMock.Object,
                wellServiceMock.Object,
                configurationMock.Object,
                backgroundWorkerMock.Object,
                emailService.Object);

            var result = await service.RemovePartsAsync(idWell, new int[] { 1005 }, CancellationToken.None);

            Assert.Equal(1, result);
        }

        [Fact]
        public async Task AddUserAsync_returns_1()
        {
            db.DrillingProgramParts.Add(new DrillingProgramPart { IdFileCategory = 1001, IdWell = idWell });
            db.SaveChanges();

            userRepositoryMock.Setup((s) => s.GetOrDefaultAsync(It.IsAny<int>(), It.IsAny<CancellationToken>()))
                .Returns(Task.FromResult(publisher1.Adapt<UserExtendedDto>()));

            var service = new DrillingProgramService(
                db,
                fileServiceMock.Object,
                userRepositoryMock.Object,
                wellServiceMock.Object,
                configurationMock.Object,
                backgroundWorkerMock.Object,
                emailService.Object);

            var result = await service.AddUserAsync(idWell, 1001, publisher1.Id, 1, CancellationToken.None);

            Assert.Equal(1, result);
        }

        [Fact]
        public async Task RemoveUserAsync_returns_1()
        {
            const int idUserRole = 1;
            const int idFileCategory = 1001;
            var entry = db.DrillingProgramParts.Add(new DrillingProgramPart
            {
                IdFileCategory = idFileCategory,
                IdWell = idWell
            });
            db.SaveChanges();
            db.RelationDrillingProgramPartUsers.Add(new RelationUserDrillingProgramPart
            {
                IdUser = publisher1.Id,
                IdDrillingProgramPart = entry.Entity.Id,
                IdUserRole = idUserRole
            });
            db.SaveChanges();
            userRepositoryMock.Setup((s) => s.GetOrDefaultAsync(It.IsAny<int>(), It.IsAny<CancellationToken>()))
                .Returns(Task.FromResult(publisher1.Adapt<UserExtendedDto>()));

            var service = new DrillingProgramService(
                db,
                fileServiceMock.Object,
                userRepositoryMock.Object,
                wellServiceMock.Object,
                configurationMock.Object,
                backgroundWorkerMock.Object,
                emailService.Object);

            var result = await service.RemoveUserAsync(idWell, idFileCategory, publisher1.Id, idUserRole, CancellationToken.None);

            Assert.Equal(1, result);
        }

        [Fact]
        public async Task AddOrReplaceFileMarkAsync_returns_1()
        {
            ConfigureNotApproved();
            fileServiceMock
                .Setup(s => s.GetOrDefaultAsync(It.IsAny<int>(), It.IsAny<CancellationToken>()))
                .Returns(Task.FromResult(file1002.Adapt<FileInfoDto>()));

            fileServiceMock
                .Setup(s => s.CreateFileMarkAsync(It.IsAny<FileMarkDto>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
                .Returns(Task.FromResult(1));

            var service = new DrillingProgramService(
                db,
                fileServiceMock.Object,
                userRepositoryMock.Object,
                wellServiceMock.Object,
                configurationMock.Object,
                backgroundWorkerMock.Object,
                emailService.Object);

            var fileMark = new FileMarkDto
            {
                IdFile = file1002.Id,
                IdMarkType = 1,
                DateCreated = DateTime.Now,
            };
            var affected = await service.AddOrReplaceFileMarkAsync(fileMark, approver1.Id, CancellationToken.None);
            Assert.Equal(1, affected);
        }

        [Fact]
        public async Task AddOrReplaceFileMarkAsync_as_replace_returns_1()
        {
            ConfigureNotApproved();
            fileServiceMock
                .Setup(s => s.GetOrDefaultAsync(It.IsAny<int>(), It.IsAny<CancellationToken>()))
                .Returns(Task.FromResult(file1002.Adapt<FileInfoDto>()));

            fileServiceMock
                .Setup(s => s.CreateFileMarkAsync(It.IsAny<FileMarkDto>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
                .Returns(Task.FromResult(1));

            var service = new DrillingProgramService(
                db,
                fileServiceMock.Object,
                userRepositoryMock.Object,
                wellServiceMock.Object,
                configurationMock.Object,
                backgroundWorkerMock.Object,
                emailService.Object);
            var fileMark = new FileMarkDto
            {
                IdFile = file1001.Id,
                IdMarkType = 0,
                DateCreated = DateTime.Now,
            };
            await service.AddOrReplaceFileMarkAsync(fileMark, approver1.Id, CancellationToken.None);
            var fileMark2 = new FileMarkDto
            {
                IdFile = file1001.Id,
                IdMarkType = 1,
                DateCreated = DateTime.Now.AddHours(1),
            };
            var affected = await service.AddOrReplaceFileMarkAsync(fileMark2, approver1.Id, CancellationToken.None);

            Assert.Equal(1, affected);
        }

        [Fact]
        public async Task MarkAsDeletedFileMarkAsync_returns_1()
        {
            ConfigureNotApproved();
            fileServiceMock
                .Setup(s => s.GetByMarkId(It.IsAny<int>(), It.IsAny<CancellationToken>()))
                .Returns(Task.FromResult(file1002.Adapt<FileInfoDto>()));

            fileServiceMock
                .Setup(s => s.MarkFileMarkAsDeletedAsync(It.IsAny<int>(), It.IsAny<CancellationToken>()))
                .Returns(Task.FromResult(1));

            var service = new DrillingProgramService(
                db,
                fileServiceMock.Object,
                userRepositoryMock.Object,
                wellServiceMock.Object,
                configurationMock.Object,
                backgroundWorkerMock.Object,
                emailService.Object);

            var fileMark = new FileMarkDto
            {
                IdFile = file1001.Id,
                IdMarkType = 0,
                DateCreated = DateTime.Now,
            };

            int idMark = 0;

            var affected = await service.MarkAsDeletedFileMarkAsync(idMark, CancellationToken.None);

            Assert.Equal(1, affected);
        }

        [Fact]
        public async Task GetStateAsync_returns_state_1()
        {
            ConfigureNotApproved();
            var service = new DrillingProgramService(
                db,
                fileServiceMock.Object,
                userRepositoryMock.Object,
                wellServiceMock.Object,
                configurationMock.Object,
                backgroundWorkerMock.Object,
                emailService.Object);

            var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None);

            Assert.Equal(1, state.IdState);
        }

        [Fact]
        public async Task GetStateAsync_returns_state_2()
        {
            ConfigureNotApproved();
            db.FileMarks.AddRange(
                new FileMark { IdFile = file1002.Id, IdUser = approver1.Id, IdMarkType = 1, DateCreated = System.DateTimeOffset.UtcNow },
                new FileMark { IdFile = file1002.Id, IdUser = approver2.Id, IdMarkType = 1, DateCreated = System.DateTimeOffset.UtcNow }
                );
            await db.SaveChangesAsync();

            wellServiceMock.Setup(s => s.GetOrDefaultAsync(It.IsAny<int>(), It.IsAny<CancellationToken>()))
                .Returns(Task.FromResult(new WellDto { Caption = "test well", Cluster = "test cluster" }));

            var service = new DrillingProgramService(
                db,
                fileServiceMock.Object,
                userRepositoryMock.Object,
                wellServiceMock.Object,
                configurationMock.Object,
                backgroundWorkerMock.Object,
                emailService.Object);

            var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None);

            Assert.Equal(2, state.IdState);
            backgroundWorkerMock.Verify(s => s.Enqueue(It.IsAny<Func<string, CancellationToken, Task>>()));
        }

        [Fact]
        public async Task GetStateAsync_returns_state_3()
        {
            ConfigureNotApproved();
            db.FileMarks.AddRange(
                new FileMark { IdFile = file1002.Id, IdUser = approver1.Id, IdMarkType = 1, DateCreated = DateTimeOffset.UtcNow },
                new FileMark { IdFile = file1002.Id, IdUser = approver2.Id, IdMarkType = 1, DateCreated = DateTimeOffset.UtcNow }
                );
            db.Files.AddRange(new FileInfo { IdCategory = 1000, IdWell = idWell, Name = "DrillingProgram.xalsx", Size = 1024 * 1024, UploadDate = DateTimeOffset.UtcNow });

            await db.SaveChangesAsync();

            wellServiceMock.Setup(s => s.GetOrDefaultAsync(It.IsAny<int>(), It.IsAny<CancellationToken>()))
                .Returns(Task.FromResult(new WellDto { Caption = "test well", Cluster = "test cluster" }));

            var service = new DrillingProgramService(
                db,
                fileServiceMock.Object,
                userRepositoryMock.Object,
                wellServiceMock.Object,
                configurationMock.Object,
                backgroundWorkerMock.Object,
                emailService.Object);

            var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None);

            Assert.Equal(3, state.IdState);
            backgroundWorkerMock.VerifyNoOtherCalls();
        }

        private void ConfigureNotApproved()
        {
            db.DrillingProgramParts.RemoveRange(db.DrillingProgramParts);
            db.Files.RemoveRange(db.Files);
            //db.RelationDrillingProgramPartUsers.RemoveRange(db.RelationDrillingProgramPartUsers);
            db.SaveChanges();

            var entry1 = db.DrillingProgramParts.Add(new DrillingProgramPart { IdWell = idWell, IdFileCategory = 1001 });
            var entry2 = db.DrillingProgramParts.Add(new DrillingProgramPart { IdWell = idWell, IdFileCategory = 1002 });
            db.SaveChanges();

            db.RelationDrillingProgramPartUsers.AddRange(new List<RelationUserDrillingProgramPart>{
                new RelationUserDrillingProgramPart{
                    IdDrillingProgramPart = entry1.Entity.Id,
                    IdUser = publisher1.Id,
                    IdUserRole = 1,
                },
                new RelationUserDrillingProgramPart{
                    IdDrillingProgramPart = entry2.Entity.Id,
                    IdUser = publisher1.Id,
                    IdUserRole = 1,
                },
                new RelationUserDrillingProgramPart{
                    IdDrillingProgramPart = entry2.Entity.Id,
                    IdUser = approver1.Id,
                    IdUserRole = 2,
                },
                new RelationUserDrillingProgramPart{
                    IdDrillingProgramPart = entry2.Entity.Id,
                    IdUser = approver2.Id,
                    IdUserRole = 2,
                },
            });

            db.Files.AddRange(new List<FileInfo>{
                file1001,
                file1002
            });

            db.SaveChanges();
        }
    }
}