using System.Collections.Generic;
using System.Linq;
using Valg.EVA.Skanning.Contracts;
using Valg.EVA.Skanning.Contracts.CommonEnums;
using Valg.EVA.Skanning.Entity.ReadModel;
using Valg.EVA.Skanning.Repositories.BallotPaper;
using Valg.EVA.Skanning.ServicesModel.ElectionConfig.DataProvider;
using Valg.EVA.Skanning.ServicesModel.Internal.BallotNumber;
using Valg.EVA.Skanning.ServicesModel.Internal.Serializers;

namespace Valg.EVA.Skanning.Commands.SaveBallotPaperVerifications
{
    public class UpdateBallotAggregatedValuesFilter : IFilter<SaveBallotPaperVerificationsCommand, bool>
    {
        private readonly IBallotPaperRepository ballotPaperRepository;
        private readonly IBallotPaperValuesUpdater ballotPaperValuesUpdater;
        private readonly ICandidateListDataProvider candidateListDataProvider;
        private readonly IElectionConfigDataProvider electionConfigDataProvider;

        public UpdateBallotAggregatedValuesFilter(IBallotPaperRepository ballotPaperRepository, 
            IBallotPaperValuesUpdater ballotPaperValuesUpdater, 
            ICandidateListDataProvider candidateListDataProvider,
            IElectionConfigDataProvider electionConfigDataProvider)
        {
            this.ballotPaperRepository = ballotPaperRepository;
            this.ballotPaperValuesUpdater = ballotPaperValuesUpdater;
            this.candidateListDataProvider = candidateListDataProvider;
            this.electionConfigDataProvider = electionConfigDataProvider;
        }

        
        
        
        public bool Execute(SaveBallotPaperVerificationsCommand command)
        {
            var optional = new CopiedUnlessSpecifiedValues();

            RejectionAndResetFieldsHelper.SetRejectionAndResetFieldsAccordingly(command, optional, ballotPaperRepository);

            var isValid = ResolveIsValid(command);
            optional.IsValidIsSpecified = true;
            optional.IsValid = isValid;

            if (!isValid)
            {
                return false;
            }

            var required = new RequiredInputBallotPaperValues
            {
                BallotPaperConcurrencyToken = command.BallotPaperConcurrencyToken,
                BallotPaperId = command.BallotPaperId,
                ChangedBy = command.ChangedBy,
                ValueSourceType = ValueSourceType.Verify,
            };

            ballotPaperValuesUpdater.UpdateBallotPaperValues(required, optional);

            return true;
        }

        private bool ResolveIsValid(SaveBallotPaperVerificationsCommand command)
        {
            if (!command.IsRejectedForAnyReason)
            {
                var fieldValuesToIncludeInValidityCheck = ballotPaperRepository.GetCurrentFieldValues(command.BallotPaperId).ToList(); 
                
                bool allWriteInsAreEitherIgnoreOrNotNeedingVerification = true;
                var ballotNoField = fieldValuesToIncludeInValidityCheck.Single(v => v.IsBallotNoField());

                if (!ballotNoField.NeedsVerification)
                {
                    SetAffiliationAndContestNameFieldsValid(fieldValuesToIncludeInValidityCheck);
                }

                if (command.BallotNumberVerification != null && command.BallotNumberVerification.IgnoreCandidates)
                {
                    RemoveAffiliationDependentFieldsFromValidityCheck(fieldValuesToIncludeInValidityCheck);
                }
                else
                {
                    if (!ballotNoField.NeedsVerification)
                    {
                        var ballotNo = ballotNoField;
                        var electionEventId = BallotNumberSplitter.GetElectionEventId(ballotNo.Value);
                        var contestId = BallotNumberSplitter.GetContestId(ballotNo.Value);
                        var affiliationShortCode = BallotNumberSplitter.GetAffiliationShortCode(ballotNo.Value);

                        RemovePersonalVotingOutsideMaxCandidatesInList(fieldValuesToIncludeInValidityCheck, electionEventId, contestId, affiliationShortCode);
                        RemoveWriteInsOutsideMaxWriteInsBounds(fieldValuesToIncludeInValidityCheck, electionEventId, contestId);

                        allWriteInsAreEitherIgnoreOrNotNeedingVerification = fieldValuesToIncludeInValidityCheck.Where(f => f.IsWriteInField()).All(f =>
                        {
                            var writeInData = WriteInDataJsonSerializer.Deserialize(f);
                            return writeInData.IgnoreWriteIn || !writeInData.NeedsVerification;
                        });
                    }
                    else
                    {
                        return false;
                    }
                }


                return fieldValuesToIncludeInValidityCheck.All(f => f.Value == null || !f.NeedsVerification)
                    && allWriteInsAreEitherIgnoreOrNotNeedingVerification;
            }

            return true;
        }

        private void SetAffiliationAndContestNameFieldsValid(List<BallotPaperFieldValue> fields)
        {
            foreach (var field in fields.Where(p => p.IsAffiliationNameField() || p.IsContestNameField()))
            {
                field.NeedsVerification = false;
            }
        }

        private void RemovePersonalVotingOutsideMaxCandidatesInList(List<BallotPaperFieldValue> fieldValuesToIncludeInValidityCheck, string electionEventId, string contestIdentifier, string affiliationShortCode)
        {
            var candidatesCount = candidateListDataProvider.GetAffiliation(electionEventId, contestIdentifier, affiliationShortCode).Candidates.Count;
            fieldValuesToIncludeInValidityCheck.RemoveAll(f => (f.IsCandidateField() || f.IsRenumberField() || f.IsStrikeoutField())
                                                && f.FieldIndex > candidatesCount);
        }

        private void RemoveWriteInsOutsideMaxWriteInsBounds(List<BallotPaperFieldValue> fieldValuesToIncludeInValidityCheck, string electionEventId, string contestId)
        {
            if (fieldValuesToIncludeInValidityCheck.Any(f => f.IsWriteInField()))
            {
                var maxWriteIns = electionConfigDataProvider.GetMaxWriteInForContest(electionEventId, contestId);

                if (maxWriteIns > 0)
                {
                    fieldValuesToIncludeInValidityCheck.RemoveAll(f => f.IsWriteInField() && f.FieldIndex > maxWriteIns);
                }
            }
        }

        private static void RemoveAffiliationDependentFieldsFromValidityCheck(List<BallotPaperFieldValue> fieldValuesToIncludeInValidityCheck)
        {
            var affiliationDependentFieldTypes = new List<FieldType>
            {
                FieldType.Candidate,
                FieldType.MarkIncr,
                FieldType.Ca_renumber,
                FieldType.MarkDecr
            };

            fieldValuesToIncludeInValidityCheck.RemoveAll(fv => affiliationDependentFieldTypes.Contains(fv.FieldType));
        }
    }
}
