using System;
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.Repositories.Counting;
using Valg.EVA.Skanning.ServicesModel.Internal.BallotNumber;
using Valg.EVA.Skanning.ServicesModel.Internal.Serializers;
using Valg.EVA.Skanning.ServicesModel.Internal.SystemSettings;
using Valg.EVA.Skanning.ServicesModel.Internal.Validation;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.Verification.Read;
using BallotPaper = Valg.EVA.Skanning.Entity.ReadModel.BallotPaper;

namespace Valg.EVA.Skanning.ServicesModel.Internal.Verification
{
    public class BallotPaperMapper : IBallotPaperMapper
    {
        private readonly IBallotPaperRepository ballotPaperRepository;
        private readonly IInitialFieldValuesResolver initialFieldValuesResolver;
        private readonly ISystemSettingsProvider systemSettingsProvider;
        private readonly ICountingRepository countingRepository;
        private readonly FillPercentageRangeCheckerFactory fillPercentageRangeCheckerFactory;

        public BallotPaperMapper(IBallotPaperRepository ballotPaperRepository, IInitialFieldValuesResolver initialFieldValuesResolver,
            ISystemSettingsProvider systemSettingsProvider, ICountingRepository countingRepository, FillPercentageRangeCheckerFactory fillPercentageRangeCheckerFactory)
        {
            this.ballotPaperRepository = ballotPaperRepository;
            this.initialFieldValuesResolver = initialFieldValuesResolver;
            this.systemSettingsProvider = systemSettingsProvider;
            this.countingRepository = countingRepository;
            this.fillPercentageRangeCheckerFactory = fillPercentageRangeCheckerFactory;
        }

        public ServiceContracts.Verification.Read.BallotPaper GetBallotPaper(int ballotPaperId)
        {
            var ballotPaperFromDatabase = ballotPaperRepository.GetBallotPaper(ballotPaperId);

            var originalFieldValues = initialFieldValuesResolver.GetInitialFieldValues(ballotPaperId);
            var currentFieldValues = ballotPaperRepository.GetCurrentFieldValues(ballotPaperId);

            var fieldValueAggregates = BallotPaperFieldValueAggregate.CreateListOfAggregates(originalFieldValues, currentFieldValues);

            var initializationValues = new BallotPaperInitializationValues();

            MapAffiliationNames(fieldValueAggregates, initializationValues, ballotPaperFromDatabase);

            MapBallotNumber(fieldValueAggregates, initializationValues, ballotPaperFromDatabase);

            MapStamp(fieldValueAggregates, initializationValues, ballotPaperFromDatabase);

            MapCandidatePersonVotes(fieldValueAggregates, initializationValues);

            initializationValues.RenumberStrikeoutVotes.AddRange(RenumberStrikeoutMapper.MapRenumberStrikeout(fieldValueAggregates,
                systemSettingsProvider.GetStrikeOutFillPercentageMinimum(),
                systemSettingsProvider.GetStrikeOutFillPercentageMaximum()));

            MapWriteIns(fieldValueAggregates, initializationValues);

            var ballotPaper = new ServiceContracts.Verification.Read.BallotPaper(ballotPaperFromDatabase.Id,
                ballotPaperFromDatabase.BoxCountingId,
                ballotPaperFromDatabase.IgnoreCandidates,
                ballotPaperFromDatabase.CurrentBallotPaperValuesId,
                ballotPaperFromDatabase.VerificationIndex,
                ballotPaperFromDatabase.RejectionReasonType.GetValueOrDefault(),
                ballotPaperFromDatabase.IsValid,
                ballotPaperFromDatabase.BallotPaperType,
                initializationValues.CandidatePersonVotes.AsReadOnly(),
                initializationValues.RenumberStrikeoutVotes.AsReadOnly(),
                initializationValues.WriteIns.AsReadOnly(),
                initializationValues.InitialWriteIns.AsReadOnly(),
                initializationValues.BallotNo,
                initializationValues.ElectionEventId,
                initializationValues.ElectionGroupId,
                initializationValues.ElectionId,
                initializationValues.ContestId,
                initializationValues.AffiliationShortCode,
                initializationValues.StampReadOnly,
                initializationValues.AffiliationNames.AsReadOnly());
            return ballotPaper;
        }

        private void MapAffiliationNames(List<BallotPaperFieldValueAggregate> fieldValueAggregates, BallotPaperInitializationValues initializationValues, BallotPaper ballotPaperFromDatabase)
        {
            var affiliationNameAggregates = fieldValueAggregates
                .Where(a => a.OriginalValue != null && a.OriginalValue.IsAffiliationNameField()
                         || a.CurrentValue != null && a.CurrentValue.IsAffiliationNameField())
                .OrderBy(a => a.CurrentValue.FieldIndex)
                .ToList();

            foreach (var affiliationNameAggregate in affiliationNameAggregates)
            {
                initializationValues.AffiliationNames.Add(MapSingleAffiliationName(affiliationNameAggregate.CurrentValue));
            }
        }

        private void MapWriteIns(List<BallotPaperFieldValueAggregate> fieldValueAggregates, BallotPaperInitializationValues initializationValues)
        {
            var writeInsAggregates = fieldValueAggregates.Where(a => (a.OriginalValue != null && a.OriginalValue.IsWriteInField())
                                                                        || (a.CurrentValue != null && a.CurrentValue.IsWriteInField()))
                                                                        .OrderBy(a => a.CurrentValue.FieldIndex)
                                                                        .ToList();

            foreach (var writeInAggregate in writeInsAggregates)
            {
                initializationValues.WriteIns.Add(MapSingleWriteIn(writeInAggregate.CurrentValue));
                initializationValues.InitialWriteIns.Add(MapSingleWriteIn(writeInAggregate.OriginalValue));
            }

        }

        private static AffiliationNameReadOnly MapSingleAffiliationName(BallotPaperFieldValue ballotPaperFieldValue)
        {
            return new AffiliationNameReadOnly(
                ballotPaperFieldValue.Value,
                ballotPaperFieldValue.FieldIndex,
                ballotPaperFieldValue.NeedsVerification
                );
        }

        private static WriteInReadOnly MapSingleWriteIn(BallotPaperFieldValue ballotPaperFieldValue)
        {
            var writeInData = WriteInDataJsonSerializer.Deserialize(ballotPaperFieldValue);

            return new WriteInReadOnly(ballotPaperFieldValue.BallotPaperFieldId,
                writeInData.NeededVerification,
                writeInData.NeedsVerification,
                writeInData.ScannerValue,
                writeInData.IgnoreWriteIn,
                writeInData.AffiliationShortCode,
                writeInData.AffiliationId,
                writeInData.CandidateName,
                writeInData.RankInAffiliation,
                ballotPaperFieldValue.FieldIndex.GetValueOrDefault());
        }

        private void MapCandidatePersonVotes(IEnumerable<BallotPaperFieldValueAggregate> fieldValueAggregates, BallotPaperInitializationValues initializationValues)
        {
            var rangeChecker = fillPercentageRangeCheckerFactory.GetPersonVoteRangeChecker();
            var personVotesAggregates = fieldValueAggregates.Where(a => (a.OriginalValue != null && a.OriginalValue.IsCandidateField())
                                                                        || (a.CurrentValue != null && a.CurrentValue.IsCandidateField())).ToList();

            if (personVotesAggregates.Count > 0)
            {
                foreach (var personVotesAggregate in personVotesAggregates)
                {
                    var personVote = CandidatePersonVoteMapper.MapSingleCandidatePersonVote(personVotesAggregate, rangeChecker);
                    initializationValues.CandidatePersonVotes.Add(personVote);
                }
            }
        }

        private void MapStamp(IEnumerable<BallotPaperFieldValueAggregate> fieldValueAggregates, BallotPaperInitializationValues initializationValues, Valg.EVA.Skanning.Entity.ReadModel.BallotPaper ballotPaperFromDatabase)
        {
            var stampAggregate = fieldValueAggregates.SingleOrDefault(a => (a.OriginalValue != null && a.OriginalValue.IsStampField())
                                                                             || (a.CurrentValue != null && a.CurrentValue.IsStampField()));

            if (stampAggregate != null)
            {
                var isValid = ballotPaperFromDatabase.RejectionReasonType != RejectionReasonType.NoOfficialStamp
                              && ValueIsGreaterThan(stampAggregate.CurrentValue.Value, systemSettingsProvider.GetStampFillPercentageMinimum());

                var isRejectedAsNoOfficialStamp = ballotPaperFromDatabase.RejectionReasonType == RejectionReasonType.NoOfficialStamp;

                var stamp = new StampReadOnly(stampAggregate.CurrentValue.NeedsVerification,
                    stampAggregate.DidOriginalValueNeedVerification(),
                    isValid,
                    isRejectedAsNoOfficialStamp);

                initializationValues.StampReadOnly = stamp;
            }
        }

        private static bool ValueIsGreaterThan(string value, int minValue)
        {
            var originalValueInt = Convert.ToInt32(value);
            return originalValueInt >= minValue;
        }

        private void MapBallotNumber(List<BallotPaperFieldValueAggregate> fieldValueAggregates, BallotPaperInitializationValues initializationValues, Valg.EVA.Skanning.Entity.ReadModel.BallotPaper ballotPaperFromDatabase)
        {
            var ballotNoAggregate = fieldValueAggregates.SingleOrDefault(a => (a.OriginalValue != null && a.OriginalValue.IsBallotNoField())
                                                                              || (a.CurrentValue != null && a.CurrentValue.IsBallotNoField()));

            if (ballotNoAggregate != null)
            {
                var ballotNumber = new BallotNumberReadOnly(ballotNoAggregate.GetOriginalValueIfPresent(),
                    ballotNoAggregate.DidOriginalValueNeedVerification(),
                    ballotNoAggregate.CurrentValue.NeedsVerification,
                    ballotNoAggregate.CurrentValue.Value,
                    ballotPaperFromDatabase.IgnoreCandidates);
                initializationValues.BallotNo = ballotNumber;

                var ballotNo = ballotNoAggregate.CurrentValue.Value;
                if (ballotNoAggregate.CurrentValue.NeedsVerification)
                {
                    var boxCounting = countingRepository.GetBoxCountingById(ballotPaperFromDatabase.BoxCountingId);
                    initializationValues.ElectionEventId = boxCounting.BarcodeLong.GetElectionEventIdFromBarcodeLong();
                    initializationValues.ElectionGroupId = boxCounting.BarcodeLong.GetElectionGroupIdFromBarcodeLong();
                    initializationValues.ElectionId = boxCounting.BarcodeLong.GetElectionIdFromBarcodeLong();
                    initializationValues.ContestId = boxCounting.BarcodeLong.GetContestIdFromBarcodeLong();
                }
                else
                {
                    initializationValues.ElectionEventId = BallotNumberSplitter.GetElectionEventId(ballotNo);
                    initializationValues.ElectionGroupId = BallotNumberSplitter.GetElectionGroupId(ballotNo);
                    initializationValues.ElectionId = BallotNumberSplitter.GetElectionId(ballotNo);
                    initializationValues.ContestId = BallotNumberSplitter.GetContestId(ballotNo);
                    initializationValues.AffiliationShortCode = BallotNumberSplitter.GetAffiliationShortCode(ballotNo);
                }
            }
        }

        private class BallotPaperInitializationValues
        {
            public BallotPaperInitializationValues()
            {
                WriteIns = new List<WriteInReadOnly>();
                InitialWriteIns = new List<WriteInReadOnly>();
                CandidatePersonVotes = new List<CandidatePersonVoteReadOnly>();
                RenumberStrikeoutVotes = new List<RenumberStrikeoutReadOnly>();
                AffiliationNames = new List<AffiliationNameReadOnly>();
            }
            public List<AffiliationNameReadOnly> AffiliationNames { get; private set; }
            public List<WriteInReadOnly> WriteIns { get; private set; }
            public List<WriteInReadOnly> InitialWriteIns { get; private set; }
            public List<CandidatePersonVoteReadOnly> CandidatePersonVotes { get; set; }
            public List<RenumberStrikeoutReadOnly> RenumberStrikeoutVotes { get; set; }
            public BallotNumberReadOnly BallotNo { get; set; }
            public string ElectionEventId { get; set; }
            public string ElectionGroupId { get; set; }
            public string ElectionId { get; set; }
            public string ContestId { get; set; }
            public string AffiliationShortCode { get; set; }
            public StampReadOnly StampReadOnly { get; set; }
        }
    }
}
