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.ServicesModel.ElectionConfig.DataProvider;
using Valg.EVA.Skanning.ServicesModel.ElectionConfig.Model;
using Valg.EVA.Skanning.ServicesModel.Internal.BallotNumber;
using Valg.EVA.Skanning.ServicesModel.Internal.Interpreter.Modifications;

namespace Valg.EVA.Skanning.ServicesModel.Internal.Interpreter
{
    public class RenumberingInterpreter
    {
        private readonly ICandidateListDataProvider candidateListDataProvider;

        public RenumberingInterpreter(ICandidateListDataProvider candidateListDataProvider)
        {
            this.candidateListDataProvider = candidateListDataProvider;
        }

        public void CheckIntegrity(BallotPaperInBoxCounting ballotPaper, IReadOnlyCollection<BallotPaperFieldValue> ballotPaperFieldValues, InterpreterModificationBuffer interpreterModificationBuffer, CountingType countingType)
        {
            var renumberFields = ballotPaperFieldValues.Where(p => p.IsRenumberField()).ToArray();
            var ballotNumberField = ballotPaperFieldValues.First(p => p.IsBallotNoField());

            if (countingType == CountingType.Final 
                && (renumberFields.Any()))
            {
                if (!ballotNumberField.NeedsVerification)
                {
                    var affiliation = GetAffiliation(ballotNumberField.Value);
                    var maxCandidateIndexForAffiliation = affiliation.Candidates.Count;

                    ValidateRenumberings(interpreterModificationBuffer, renumberFields, maxCandidateIndexForAffiliation);
                }
                else
                {
                    ValidateRenumberings(interpreterModificationBuffer, renumberFields, 0);
                }
            }
        }

        private void ValidateRenumberings(InterpreterModificationBuffer interpreterModificationBuffer, BallotPaperFieldValue[] renumberFields, int maxCandidateIndexForAffiliation)
        {
            foreach (var renumberField in renumberFields)
            {
                if (!renumberField.NeedsVerification && !FieldIsAlreadyInModificationBuffer(interpreterModificationBuffer, renumberField))
                {
                    var outsideBounds = IsRenumberGivenOutsideBounds(maxCandidateIndexForAffiliation, renumberField);
                    if (outsideBounds)
                    {
                        renumberField.NeedsVerification = true;
                        interpreterModificationBuffer.AddModifiedField(renumberField);
                    }
                    else
                    {
                        var sameValueAsCurrentExistsOnOtherRenumberFields = renumberFields
                            .Where(f => f.Value.IsStringActuallyLongOrInt() && !IsRenumberGivenOutsideBounds(maxCandidateIndexForAffiliation, f) && !ReferenceEquals(f, renumberField))
                            .Any(f => f.Value == renumberField.Value);
                        if (sameValueAsCurrentExistsOnOtherRenumberFields)
                        {
                            renumberField.NeedsVerification = true;
                            interpreterModificationBuffer.AddModifiedField(renumberField);
                        }
                    }
                }
            }
        }

        private static bool IsRenumberGivenOutsideBounds(int maxCandidateIndexForAffiliation, BallotPaperFieldValue renumberField)
        {
            var renumberFieldValue = int.Parse(renumberField.Value);
            var outsideBounds = renumberFieldValue < 1 || renumberFieldValue > maxCandidateIndexForAffiliation;
            return outsideBounds;
        }

        private bool FieldIsAlreadyInModificationBuffer(InterpreterModificationBuffer interpreterModificationBuffer, BallotPaperFieldValue fieldValue)
        {
            return interpreterModificationBuffer.GetModifiedFields().ToList().Exists(m => m.BallotPaperFieldId == fieldValue.BallotPaperFieldId);
        }

        private Affiliation GetAffiliation(string ballotNumber)
        {
            return candidateListDataProvider.GetAffiliation(
                BallotNumberSplitter.GetElectionEventId(ballotNumber),
                BallotNumberSplitter.GetContestId(ballotNumber),
                BallotNumberSplitter.GetAffiliationShortCode(ballotNumber));
        }
    }
}
