using System;
using System.Collections.ObjectModel;
using System.Linq;
using Valg.EVA.Skanning.Contracts.CommonEnums;
using Valg.EVA.Skanning.Entity.ReadModel;
using Valg.EVA.Skanning.Entity.SerializedEntities.FieldValueModel;
using Valg.EVA.Skanning.Entity.WriteModel;
using Valg.EVA.Skanning.Repositories.BallotPaper;
using Valg.EVA.Skanning.ServicesModel.Internal;
using Valg.EVA.Skanning.ServicesModel.Internal.Serializers;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.Verification.Write;

namespace Valg.EVA.Skanning.Commands.SaveBallotPaperVerifications
{
    public class UpdateBallotPersonalVotingFeaturesFilter : IFilter<UpdateBallotPersonalVotingFeaturesFilterInput, Empty>
    {
        private readonly IBallotPaperWritableRepository ballotPaperWritableRepository;
        private readonly FieldValueOperationsHelper fieldValueOperationsHelper;

        public UpdateBallotPersonalVotingFeaturesFilter(IBallotPaperWritableRepository ballotPaperWritableRepository, FieldValueOperationsHelper fieldValueOperationsHelper)
        {
            this.ballotPaperWritableRepository = ballotPaperWritableRepository;
            this.fieldValueOperationsHelper = fieldValueOperationsHelper;
        }

        public Empty Execute(UpdateBallotPersonalVotingFeaturesFilterInput input)
        {
            if (input.KeepPersonalVotingFeatures)
            {
                var command = input.Command;
                var currentFieldValues = input.CurrentFieldValues;

                InsertPersonVotes(command);

                UpdateWriteIns(command, currentFieldValues);

                InsertRenumberAndStrikeouts(command, currentFieldValues);
            }

            return Empty.Value;
        }

        private void InsertPersonVotes(SaveBallotPaperVerificationsCommand command)
        {
            if (command.PersonVoteVerifications != null && command.PersonVoteVerifications.Length > 0)
            {
                foreach (var personVoteVerification in command.PersonVoteVerifications)
                {
                    var newActualValue = (personVoteVerification.IsVoteGiven ? ScannerFieldValueTemplateValues.PersonVoteIsGiven : 0).ToString();

                    if (personVoteVerification.PersonVoteFieldId.HasValue)
                    {
                        fieldValueOperationsHelper.InsertBallotPaperFieldValue(newActualValue, personVoteVerification.PersonVoteFieldId.Value, ValueSourceType.Verify, command.ChangedBy);
                    }
                    else
                    {
                        fieldValueOperationsHelper.InsertBallotPaperField(newActualValue, command.BallotPaperId, personVoteVerification.CandidateIndex, FieldType.MarkIncr, command.ChangedBy);
                    }
                }
            }
        }

        private void UpdateWriteIns(SaveBallotPaperVerificationsCommand command, ReadOnlyCollection<BallotPaperFieldValue> currentFieldValues)
        {
            if (command.WriteInVerifications != null)
            {
                foreach (var writeInVerification in command.WriteInVerifications)
                {
                    var currentFieldValue = currentFieldValues.SingleOrDefault(f => f.BallotPaperFieldId == writeInVerification.BallotPaperFieldId);
                    if (currentFieldValue != null)
                    {
                        var writeInData = MapWriteInDataValues(currentFieldValue, writeInVerification, command.BallotPaperId);

                        ballotPaperWritableRepository.InsertWriteInBallotPaperFieldValue(new WriteInBallotPaperFieldValueInsert
                        {
                            BallotPaperFieldId = currentFieldValue.BallotPaperFieldId,
                            ValueSourceTypeId = ValueSourceType.Verify,
                            WriteInData = writeInData,
                            CreatedBy = command.ChangedBy,
                            NeedsVerification = false,
                        });
                    }
                    else
                    {
                        throw new ArgumentException(string.Format("When attepting to save writeIn, there was no matching row in the database to apply the update on. This is probably an indication of a programming error. BallotPaperId = {0}, Attempted fieldId for writeIn = {1}",
                            command.BallotPaperId, writeInVerification.BallotPaperFieldId));
                    }
                }
            }
        }

        private void InsertRenumberAndStrikeouts(SaveBallotPaperVerificationsCommand command, ReadOnlyCollection<BallotPaperFieldValue> currentFieldValues)
        {
            if (command.RenumberStrikeoutVerifications != null && command.RenumberStrikeoutVerifications.Length > 0)
            {
                foreach (var verification in command.RenumberStrikeoutVerifications)
                {
                    if (verification is RenumberIsGivenVerification)
                    {
                        var renumber = (RenumberIsGivenVerification)verification;

                        InsertFieldOrFieldValue(command, currentFieldValues, FieldType.Ca_renumber, verification, renumber.RenumberedTo.ToString());
                        InsertFieldValueIfFieldExists(command, currentFieldValues, FieldType.MarkDecr, verification, ScannerFieldValueTemplateValues.FieldIsIgnored);
                    }
                    else if (verification is StrikeoutIsGivenVerification)
                    {
                        InsertFieldOrFieldValue(command, currentFieldValues, FieldType.MarkDecr, verification, ScannerFieldValueTemplateValues.StrikeoutIsGiven.ToString());
                        InsertFieldValueIfFieldExists(command, currentFieldValues, FieldType.Ca_renumber, verification, ScannerFieldValueTemplateValues.FieldIsIgnored);
                    }
                    else
                    {
                        InsertFieldOrFieldValue(command, currentFieldValues, FieldType.Ca_renumber, verification, ScannerFieldValueTemplateValues.FieldIsIgnored);
                        InsertFieldOrFieldValue(command, currentFieldValues, FieldType.MarkDecr, verification, ScannerFieldValueTemplateValues.FieldIsIgnored);
                    }
                }
            }
        }

        private void InsertFieldOrFieldValue(SaveBallotPaperVerificationsCommand command, 
            ReadOnlyCollection<BallotPaperFieldValue> 
            currentFieldValues, 
            FieldType fieldType, 
            IRenumberStrikeoutVerification verification,
           string value)
        {
            if (!InsertFieldValueIfFieldExists(command, currentFieldValues, fieldType, verification, value))
            {
                fieldValueOperationsHelper.InsertBallotPaperField(value, command.BallotPaperId, verification.CandidateIndex, fieldType, command.ChangedBy);
            }
        }

        private bool InsertFieldValueIfFieldExists(SaveBallotPaperVerificationsCommand command, ReadOnlyCollection<BallotPaperFieldValue> currentFieldValues, FieldType fieldType, IRenumberStrikeoutVerification verification, string value)
        {
            var existingFieldForIndex = currentFieldValues.SingleOrDefault(f => f.FieldType == fieldType && f.FieldIndex == verification.CandidateIndex); 
            if (existingFieldForIndex != null)
            {
                fieldValueOperationsHelper.InsertBallotPaperFieldValue(value, existingFieldForIndex.BallotPaperFieldId, ValueSourceType.Verify, command.ChangedBy);
                return true;
            }

            return false;
        }

        private WriteInData MapWriteInDataValues(BallotPaperFieldValue currentFieldValue, IWriteInVerification writeInVerification, int ballotPaperId)
        {
            var existingWriteInData = WriteInDataJsonSerializer.Deserialize(currentFieldValue);

            if (existingWriteInData.NeedsVerification != currentFieldValue.NeedsVerification)
            {
                throw new InvalidOperationException(string.Format("Inconsistency in writein.NeedsVerification and ballotPaperFieldValue.NeedsVerification. This is probably an indication of a programming error. BallotPaperId = {0}",
                    ballotPaperId));
            }

            existingWriteInData.NeedsVerification = false;

            if (writeInVerification is WriteInIsIgnoredVerification)
            {
                existingWriteInData.IgnoreWriteIn = true;
            }
            else
            {
                existingWriteInData.IgnoreWriteIn = false;

                var asVerification = (WriteInIsVerifiedVerification)writeInVerification;
                existingWriteInData.AffiliationId = asVerification.AffiliationId;
                existingWriteInData.AffiliationShortCode = asVerification.AffiliationShortCode;
                existingWriteInData.CandidateName = asVerification.CandidateName;
                existingWriteInData.RankInAffiliation = asVerification.RankInAffiliation;
            }

            return existingWriteInData;
        }
    }
}
