using System.Collections.ObjectModel;
using Valg.EVA.Skanning.Contracts.CommonEnums;
using Valg.EVA.Skanning.Entity.ReadModel;
using Valg.EVA.Skanning.Entity.SerializedEntities.CountResultModel;
using Valg.EVA.Skanning.Entity.SqlConnection;
using Valg.EVA.Skanning.Entity.WriteModel;
using Valg.EVA.Skanning.Repositories.Counting;
using Valg.EVA.Skanning.Repositories.CountingResult;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Valg.EVA.Skanning.Repositories.Transfers;
using Valg.EVA.Skanning.ServicesModel.Internal.CountResult;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.CountResult.Write;

namespace Valg.EVA.Skanning.Commands.CreateCountingResult
{
    public class CreateCountingResultCommandHandler : IHandleCommandsWithReturnValue<CreateCountingResultCommand, CreateCountingResultCommandResult>
    {
        private readonly ICountingRepository countingRepository;
        private readonly CastVoteMapper castVoteMapper;
        private readonly ICountingResultWritableRepository countingResultWritableRepository;
        private readonly ICountingWriteableRepository countingWriteableRepository;
        private readonly TransactionScopeFactory transactionScopeFactory;
        private readonly ITransferWriteableRepository transferWriteableRepository;

        public CreateCountingResultCommandHandler(ICountingRepository countingRepository, CastVoteMapper castVoteMapper, ICountingResultWritableRepository countingResultWritableRepository,
            ICountingWriteableRepository countingWriteableRepository, TransactionScopeFactory transactionScopeFactory, ITransferWriteableRepository transferWriteableRepository)
        {
            this.countingRepository = countingRepository;
            this.castVoteMapper = castVoteMapper;
            this.countingResultWritableRepository = countingResultWritableRepository;
            this.countingWriteableRepository = countingWriteableRepository;
            this.transactionScopeFactory = transactionScopeFactory;
            this.transferWriteableRepository = transferWriteableRepository;
        }

        public void HandleCommand(CreateCountingResultCommand command)
        {
            using (var scope = transactionScopeFactory.GetTransactionScope(60 * 5))
            {
                
                var boxCountings = countingRepository.GetBoxCountingsByCountingId(command.CountingId);

                if (AreBoxesStillLocked(boxCountings))
                {
                    var counting = countingRepository.GetCountingByBoxCountingId(boxCountings.First().Id);

                    if (command.IsInEmergencyMode && !counting.IsProcessedInEmergencyMode)
                    {
                        countingWriteableRepository.MarkCountingAsProcessedDuringEmergencMode(counting.Id);
                    }

                    var castVotes = castVoteMapper.MapToCastVotes(counting, command.ReportingUnitIdentifier);

                    Save(command.CountingId, castVotes);

                    SetBoxCountingsComplete(command.CountingId, command.BoxCountingsToLock, command.GeneratedBy);
                    countingWriteableRepository.UpdateCountingState(counting.Id, CountingState.Complete, command.CountingConcurrencyToken, CountingState.Locked, command.GeneratedBy);

                    var transferJobId = transferWriteableRepository.InsertTransferJob(command.CountingId, command.GeneratedBy);

                    CommandResult = CreateCountingResultCommandResult.Success(transferJobId);

                    scope.Complete();
                }
                else
                {
                    CommandResult = CreateCountingResultCommandResult.Fail();
                }
            }
        }

        private void SetBoxCountingsComplete(int countingId, Dictionary<int, BoxCountingToLock> boxCountingsToLock, string changedBy)
        {
            
            var enumerator = boxCountingsToLock.GetEnumerator();
            while (enumerator.MoveNext())
            {
                var boxCountingToLock = enumerator.Current.Value;

                countingWriteableRepository.UpdateBoxCountingState(boxCountingToLock.BarcodeLong, boxCountingToLock.BarcodeShort, countingId, BoxCountingState.Complete,
                    boxCountingToLock.ConcurrencyToken, BoxCountingState.Locked, changedBy);
            }
        }

        private void Save(int countingId, ReadOnlyCollection<CastVote> castVotes)
        {
            foreach (var castVote in castVotes)
            {
                countingResultWritableRepository.InsertCountingResult(new CountingResultInsert
                {
                    CountingId = countingId,
                    BallotPaperId = castVote.BallotDbId,
                    CastVote = castVote
                });
            }
        }

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

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