using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Valg.EVA.Skanning.Contracts;
using Valg.EVA.Skanning.Contracts.CommonEnums;
using Valg.EVA.Skanning.Entity.ReadModel;
using Valg.EVA.Skanning.Entity.SqlConnection;
using Valg.EVA.Skanning.Entity.WriteModel;
using Valg.EVA.Skanning.Repositories.BallotPaper;
using Valg.EVA.Skanning.Repositories.Counting;
using Valg.EVA.Skanning.ServicesModel.Internal.Interpreter.Modifications;
using Valg.EVA.Skanning.ServicesModel.Internal.Serializers;

namespace Valg.EVA.Skanning.Commands.SaveInterpreterModifications
{
    public class SaveInterpreterModificationsCommandHandler : IHandleCommands<SaveInterpreterModificationsCommand>
    {
        private readonly IBallotPaperValuesUpdater ballotPaperValuesUpdater;
        private readonly TransactionScopeFactory transactionScopeFactory;
        private readonly ICountingWriteableRepository countingWriteableRepository;
        private readonly IBallotPaperWritableRepository ballotPaperWritableRepository;

        public SaveInterpreterModificationsCommandHandler(
            ICountingWriteableRepository countingWriteableRepository,
            IBallotPaperWritableRepository ballotPaperWritableRepository,
            IBallotPaperValuesUpdater ballotPaperValuesUpdater, 
            TransactionScopeFactory transactionScopeFactory)
        {
            this.ballotPaperValuesUpdater = ballotPaperValuesUpdater;
            this.transactionScopeFactory = transactionScopeFactory;
            this.countingWriteableRepository = countingWriteableRepository;
            this.ballotPaperWritableRepository = ballotPaperWritableRepository;
        }

        public void HandleCommand(SaveInterpreterModificationsCommand command)
        {
            using (var scope = transactionScopeFactory.GetTransactionScope())
            {
                SaveModifiedFieldsChanges(command.ModifiedFields, command.ChangedBy);
                UpdateBallotPapers(command.BallotPaperValuesToUpdate, command.ChangedBy);

                UpdateCountingToStateReadyForVerify(command);

                scope.Complete();
            }
        }

        private void UpdateBallotPapers(ReadOnlyCollection<BallotPaperValuesInterpreterModification> ballotPapers, string changedBy)
        {
            if (ballotPapers != null)
            {
                
                foreach (var ballotPaper in ballotPapers)
                {
                    UpdateBallotPapers(changedBy, ballotPaper);
                }
            }
        }

        private void UpdateBallotPapers(string changedBy, BallotPaperValuesInterpreterModification ballotPaper)
        {
            AssertConsistentNeedsVerificationAndIsValid(ballotPaper);

            var required = new RequiredInputBallotPaperValues
            {
                BallotPaperId = ballotPaper.BallotPaperId,
                ChangedBy = changedBy,
                ValueSourceType = ValueSourceType.StateInterpreter,
                BallotPaperConcurrencyToken = ballotPaper.BallotPaperConcurrencyToken
            };

            var optional = new CopiedUnlessSpecifiedValues();

            if (ballotPaper.IgnoreCandidates)
            {
                optional.IgnoreCandidatesIsSpecified = true;
                optional.IgnoreCandidates = true;
            }

            if (ballotPaper.NeedsVerification.HasValue)
            {
                optional.NeededVerificationIsSpecified = true;
                var neededVerification = ballotPaper.NeedsVerification.Value;
                optional.NeededVerification = neededVerification;

                if (neededVerification)
                {
                    optional.IsValidIsSpecified = true;
                    optional.IsValid = false;
                }
            }

            if (ballotPaper.VerificationIndex.HasValue)
            {
                optional.VerificationIndexIsSpecified = true;
                optional.VerificationIndex = ballotPaper.VerificationIndex;
            }

            if (ballotPaper.IsValid.HasValue)
            {
                optional.IsValidIsSpecified = true;
                optional.IsValid = ballotPaper.IsValid.Value;
            }

            ballotPaperValuesUpdater.UpdateBallotPaperValues(required, optional);
        }

        private void AssertConsistentNeedsVerificationAndIsValid(BallotPaperValuesInterpreterModification ballotPaper)
        {
            if (ballotPaper.NeedsVerification.HasValue && ballotPaper.IsValid.HasValue && ballotPaper.NeedsVerification == ballotPaper.IsValid)
                throw new InvalidOperationException("Cannot specify same value for NeedsVerification and IsValid.");
        }

        private void SaveModifiedFieldsChanges(IEnumerable<BallotPaperFieldValue> ballotPaperFieldValues, string changedBy)
        {
            if (ballotPaperFieldValues != null)
            {
                foreach (var paperFieldValue in ballotPaperFieldValues)
                {
                    var fieldToInsert = new BallotPaperFieldValueInsert();
                    fieldToInsert.BallotPaperFieldId = paperFieldValue.BallotPaperFieldId;
                    fieldToInsert.NeedsVerification = paperFieldValue.NeedsVerification;
                    fieldToInsert.Value = paperFieldValue.Value;
                    fieldToInsert.ValueSourceTypeId = ValueSourceType.StateInterpreter;
                    fieldToInsert.CreatedBy = changedBy;
                    if (paperFieldValue.IsWriteInField())
                    {
                        AssertConsistentNeedsVerificationAndWriteInNeedsVerification(fieldToInsert);
                    }

                    ballotPaperWritableRepository.InsertBallotPaperFieldValue(fieldToInsert);
                }
            }
        }

        private void AssertConsistentNeedsVerificationAndWriteInNeedsVerification(BallotPaperFieldValueInsert fieldToInsert)
        {
            var writeInData = WriteInDataJsonSerializer.Deserialize(fieldToInsert);
            if (writeInData.NeedsVerification || fieldToInsert.NeedsVerification)
            {
                if (!(writeInData.NeededVerification && writeInData.NeedsVerification && fieldToInsert.NeedsVerification))
                {
                    throw new InvalidOperationException("Inconsistency in needsVerification for writeins detected when saving data from interpreter. This is an indication of a programming error.");
                }
            } 
        }

        private void UpdateCountingToStateReadyForVerify(SaveInterpreterModificationsCommand command)
        {
            countingWriteableRepository.UpdateBoxCountingState(command.BoxCountingBarcodeLong, command.BoxCountingBarcodeShort, command.CountingId, BoxCountingState.ReadyForVerify, command.BoxCountingLastModified, BoxCountingState.ScanComplete, command.ChangedBy);
        }        
    }
}
