using System.Collections.Generic;
using System.Linq;
using Valg.EVA.Skanning.Contracts.CommonEnums;
using Valg.EVA.Skanning.Entity.ReadModel;
using Valg.EVA.Skanning.Entity.SqlConnection;
using Valg.EVA.Skanning.Repositories.Counting;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.CountResult.Write;

namespace Valg.EVA.Skanning.Commands.SetCountingLocked
{
    public class SetCountingLockedCommandHandler : IHandleCommandsWithReturnValue<SetCountingLockedCommand, SetCountingLockedCommandResult>
    {
        private readonly ICountingRepository countingRepository;
        private readonly ICountingWriteableRepository countingWriteableRepository;
        private readonly TransactionScopeFactory transactionScopeFactory;

        public SetCountingLockedCommandHandler(ICountingRepository countingRepository, ICountingWriteableRepository countingWriteableRepository, TransactionScopeFactory transactionScopeFactory)
        {
            this.countingRepository = countingRepository;
            this.countingWriteableRepository = countingWriteableRepository;
            this.transactionScopeFactory = transactionScopeFactory;
        }

        public void HandleCommand(SetCountingLockedCommand command)
        {
            using (var scope = transactionScopeFactory.GetTransactionScope())
            {
                var boxCountings = countingRepository.GetBoxCountingsByCountingId(command.CountingId);

                if (PreCheckConcurrency(boxCountings, command))
                {
                    if (!AreAlreadyLocked(boxCountings))
                    {
                        if (AreAllBallotPapersVerified(boxCountings))
                        {
                            UpdateCountingStateAndBoxCountingState(command);

                            CommandResult = ReloadBoxCountingsAndCreateResult(command.CountingId);
                        }
                        else
                        {
                            CommandResult = SetCountingLockedCommandResult.FailedResult;
                        }   
                    }
                    else
                    {
                        CommandResult = new SetCountingLockedCommandResult(command.CountingConcurrencyToken, command.ExpectedBoxCountingsToLock);
                    }
                }
                else
                {
                    CommandResult = SetCountingLockedCommandResult.FailedResult;
                }
                scope.Complete();
            }
        }

        private SetCountingLockedCommandResult ReloadBoxCountingsAndCreateResult(int countingId)
        {
            var boxCountings = countingRepository.GetBoxCountingsByCountingId(countingId);
            var first = boxCountings.First();
            return new SetCountingLockedCommandResult(first.CountingModified, 
                boxCountings.Select(b =>  new BoxCountingToLock(b.Id, b.Modified, b.BarcodeLong, b.BarcodeShort, b.State)).ToDictionary(k => k.BoxCountingId));
        }

        private void UpdateCountingStateAndBoxCountingState(SetCountingLockedCommand command)
        {
            foreach (var boxCountingToLock in command.ExpectedBoxCountingsToLock)
            {
                if (boxCountingToLock.Value.BoxCountingState == BoxCountingState.Empty)
                {
                    countingWriteableRepository.UpdateBoxCountingState(boxCountingToLock.Value.BarcodeLong, boxCountingToLock.Value.BarcodeShort, command.CountingId, BoxCountingState.Locked, boxCountingToLock.Value.ConcurrencyToken, BoxCountingState.Empty, command.LockedBy);
                }
                else
                {
                    countingWriteableRepository.UpdateBoxCountingState(boxCountingToLock.Value.BarcodeLong, boxCountingToLock.Value.BarcodeShort, command.CountingId, BoxCountingState.Locked, boxCountingToLock.Value.ConcurrencyToken, BoxCountingState.ReadyForVerify, command.LockedBy);
                }
            }

            countingWriteableRepository.UpdateCountingState(command.CountingId, CountingState.Locked, command.CountingConcurrencyToken, CountingState.Active, command.LockedBy);
        }

        private bool AreAllBallotPapersVerified(List<BoxCounting> boxCountings)
        {
            var first = boxCountings.First();
            if (first.CountingType == CountingType.Final)
            {
                return !countingRepository.AreThereAnyNonValidBallotPapersInThisCounting(first.CountingId);
            }

            return true;
        }

        private bool AreAlreadyLocked(List<BoxCounting> boxCountings)
        {
            return boxCountings.All(b => b.State == BoxCountingState.Locked && b.CountingState == CountingState.Locked);
        }

        private bool PreCheckConcurrency(List<BoxCounting> boxCountings, SetCountingLockedCommand command)
        {
            var equalNumberOfBoxes = boxCountings.Count == command.ExpectedBoxCountingsToLock.Count;

            var allHaveExpectedConcurrencyInformation = boxCountings.All(b =>
            {
                return b.CountingModified == command.CountingConcurrencyToken
                       && command.ExpectedBoxCountingsToLock.ContainsKey(b.Id)
                       && command.ExpectedBoxCountingsToLock[b.Id].ConcurrencyToken == b.Modified;
            });

            return equalNumberOfBoxes && allHaveExpectedConcurrencyInformation;
        }

        public SetCountingLockedCommandResult CommandResult { get; private set; }
    }
}
