using System;
using System.Collections.Generic;
using System.Linq;
using Prism.Events;
using Valg.EVA.Skanning.Contracts.CommonEnums;
using Valg.EVA.Skanning.Identity;
using Valg.EVA.Skanning.Services.Verification;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.Verification.Read;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.Verification.Write;
using Valg.EVA.Skanning.Verify.Startup.VerifyBallotPaper.BallotNumber;
using Valg.EVA.Skanning.Verify.Startup.VerifyBallotPaper.Messaging.Events;

namespace Valg.EVA.Skanning.Verify.Startup.VerifyBallotPaper.WriteModel
{
    public class WriteModelHub : IWriteModelHub
    {
        private readonly IEventAggregator eventAggregator;
        private readonly IVerificationService verificationService;
        private readonly IIdentityProvider identityProvider;
        private readonly List<BallotNumberModification> ballotNumberModifications = new List<BallotNumberModification>();
        private readonly List<PersonVoteModification> personVoteModifications = new List<PersonVoteModification>();
        private readonly List<IRenumberStrikeoutModification> renumberStrikeoutModifications = new List<IRenumberStrikeoutModification>();
        private readonly List<RejectionReasonModification> rejectionReasonModifications = new List<RejectionReasonModification>();
        private readonly List<StampValidityModification> stampValidityModifications = new List<StampValidityModification>();
        private readonly Dictionary<int, IWriteInModification> writeInValueModifications = new Dictionary<int, IWriteInModification>();
        private readonly List<AffiliationNameModification> affiliationNameModifications = new List<AffiliationNameModification>();

        public WriteModelHub(IEventAggregator eventAggregator, IVerificationService verificationService, IIdentityProvider identityProvider)
        {
            this.eventAggregator = eventAggregator;
            this.verificationService = verificationService;
            this.identityProvider = identityProvider;
        }

        public void AddBallotNumberModification(BallotNumberModification modification)
        {
            ballotNumberModifications.Add(modification);

            personVoteModifications.Clear();
            writeInValueModifications.Clear();
            renumberStrikeoutModifications.Clear();

            var update = new BallotNumberUpdate(modification.BallotNumber, modification.IgnoreCandidates);
            eventAggregator.GetEvent<BallotNumberUpdatedEvent>().Publish(new BallotNumberUpdatedEventArgs(update));
        }

        public void AddStampValidityModification(StampValidityModification stampValidityModification)
        {
            stampValidityModifications.Add(stampValidityModification);
        }

        public void AddPersonVoteModification(int candidateIndex, bool isVoteGiven, int? personVoteFieldId)
        {
            personVoteModifications.Add(
                new PersonVoteModification(personVoteFieldId, isVoteGiven, candidateIndex, DateTime.Now));
        }

        public void AddRenumberStrikeoutModification(int candidateIndex, int? renumber, bool? strikeout)
        {
            IRenumberStrikeoutModification modification;
            if (renumber == null && strikeout == null)
            {
                modification = new NoRenumberOrStrikeoutModification(candidateIndex, DateTime.Now);
            }
            else if (renumber.HasValue)
            {
                modification = new RenumberIsGivenModification(candidateIndex, renumber.Value, DateTime.Now);
            }
            else
            {
                modification = new StrikeoutIsGivenModification(candidateIndex, DateTime.Now);
            }

            renumberStrikeoutModifications.Add(modification);
        }

        public RejectionReasonModification GetLatestRejectionReasonModification()
        {
            return rejectionReasonModifications.LastOrDefault();
        }

        private PersonVoteModification[] GetLatestPersonVoteModifications()
        {
            var orderedByTimestamp = personVoteModifications.OrderByDescending(m => m.Timestamp);
            var modificationsToReturn = new List<PersonVoteModification>();
            foreach (var personVoteModification in orderedByTimestamp)
            {
                if (!modificationsToReturn.Exists(p => p.CandidateIndex == personVoteModification.CandidateIndex && p.PersonVoteFieldId == personVoteModification.PersonVoteFieldId))
                {
                    modificationsToReturn.Add(personVoteModification);
                }
            }

            return modificationsToReturn.ToArray();
        }

        public IRenumberStrikeoutModification[] GetLatestRenumberStrikeoutModifications()
        {
            var orderedByTimestamp = renumberStrikeoutModifications.OrderByDescending(m => m.Timestamp);
            var modificationsToReturn = new List<IRenumberStrikeoutModification>();
            foreach (var renumberStrikeoutModification in orderedByTimestamp)
            {
                if (!modificationsToReturn.Exists(p => p.CandidateIndex == renumberStrikeoutModification.CandidateIndex))
                {
                    modificationsToReturn.Add(renumberStrikeoutModification);
                }
            }

            return modificationsToReturn.ToArray();
        }

        public void Clear()
        {
            ballotNumberModifications.Clear();
            personVoteModifications.Clear();
            renumberStrikeoutModifications.Clear();
            rejectionReasonModifications.Clear();
            stampValidityModifications.Clear();
            writeInValueModifications.Clear();
        }

        public bool HasAnyPersonVoteModificationsBeenMade => personVoteModifications.Any();

        public bool HasAnyBallotNumberModificationsBeenMade => ballotNumberModifications.Any();

        public bool HasAnyRejectionReasonModificationsBeenMade => rejectionReasonModifications.Any();

        public bool HasAnyStampValidityModificationsBeenMade => stampValidityModifications.Any();

        public bool HasAnyWriteInModificationsBeenMade => writeInValueModifications.Any();

        public RejectionReasonType RejectionReason { get; private set; }

        public bool IsRejected => RejectionReason != RejectionReasonType.NotRejected;

        public int? PageIndexRejectedOn { get; private set; }

        public bool AreThereAnyPersonVoteModificationsForThisCandidate(int candidateIndex)
        {
            return personVoteModifications.Any(m => m.CandidateIndex == candidateIndex);
        }

        public void AddWriteInModification(IWriteInModification writeInModification)
        {
            var key = writeInModification.BallotPaperFieldId;

            if (writeInValueModifications.ContainsKey(key))
            {
                writeInValueModifications[key] = writeInModification;
            }
            else
            {
                writeInValueModifications.Add(writeInModification.BallotPaperFieldId, writeInModification);
            }
        }

        public IWriteInModification[] GetLatestWriteInModifications()
        {
            return writeInValueModifications.Values.ToArray();
        }

        public void SetRejectionReason(RejectionReasonType reason, int? pageIndexRejected, bool isModification = true)
        {
            if (reason != RejectionReason)
            {
                var previousReason = RejectionReason;

                if (isModification)
                {
                    rejectionReasonModifications.Add(new RejectionReasonModification(reason));
                }

                PageIndexRejectedOn = pageIndexRejected;
                RejectionReason = reason;

                if (isModification)
                {
                    PublishRejectionReasonChanged(previousReason);
                }
            }
        }

        public void SetNotRejected(RejectionReasonType[] canCancelAnyOfTheseRejectionReasons)
        {
            if (canCancelAnyOfTheseRejectionReasons.Contains(RejectionReason))
            {
                SetRejectionReason(RejectionReasonType.NotRejected, null);
            }
        }

        private void PublishRejectionReasonChanged(RejectionReasonType previousReason)
        {
            var ev = eventAggregator.GetEvent<RejectionReasonChangedEvent>();
            var args = new RejectionReasonChangedEventArgs(RejectionReason, previousReason);

            if (args.CurrentRejectionReason != RejectionReasonType.NotRejected)
            {
                personVoteModifications.Clear();
                writeInValueModifications.Clear();
                renumberStrikeoutModifications.Clear();
            }

            ev.Publish(args);
        }

        public SaveBallotPaperVerificationsResult SaveBallotPaperForVerification(BallotPaper ballotPaper)
        {
            MapAffilationVerification();
            var parameters = new SaveBallotPaperVerificationsParameters(
                MapBallotNumberVerification(),
                MapPersonVoteVerifications(),
                ballotPaper.Id,
                ballotPaper.BallotPaperConcurrencyToken,
                MapRejectionReasonVerification(),
                MapStampVerification(),
                identityProvider.GetCurrentUserName(),
                MapWriteInVerifications(),
                identityProvider.IsInEmergencyMode,
                MapRenumberStrikeoutVerifications(),
                identityProvider.CurrentSelectedRole.CountingEmlAreaContextId,
                MapAffilationVerification());

            return verificationService.SaveBallotPaperVerifications(parameters);
        }

        private bool MapAffilationVerification()
        {
            return affiliationNameModifications.All(p => p.Valid);
        }

        public bool RevokeBallotPaperSeenStatus(int ballotPaperConcurrencyToken, int ballotPaperId)
        {
            var revocationResult = verificationService.RevokeBallotPaperSeenStatus(new RevokeBallotPaperSeenStatusParameters(
                ballotPaperConcurrencyToken, ballotPaperId,
                identityProvider.GetCurrentUserName()));

            return revocationResult.BallotWasRevoked;
        }

        public void AddAffiliationNameModification(AffiliationNameModification affiliationNameModification)
        {
            affiliationNameModifications.Add(affiliationNameModification);
        }

        private StampVerification MapStampVerification()
        {
            var from = stampValidityModifications.LastOrDefault();
            if (from != null)
            {
                return new StampVerification
                {
                    StampIsValid = from.StampIsValid
                };
            }

            return null;
        }

        private RejectionReasonVerification MapRejectionReasonVerification()
        {
            var from = GetLatestRejectionReasonModification();
            if (from != null)
            {
                return new RejectionReasonVerification
                {
                    NewValue = from.NewValue
                };
            }

            return null;
        }

        private PersonVoteVerification[] MapPersonVoteVerifications()
        {
            var latestPersonVoteModifications = GetLatestPersonVoteModifications();
            return latestPersonVoteModifications
                .Select(from => new PersonVoteVerification
                {
                    CandidateIndex = from.CandidateIndex,
                    IsVoteGiven = from.IsVoteGiven,
                    PersonVoteFieldId = from.PersonVoteFieldId
                }).ToArray();
        }

        private BallotNumberVerification MapBallotNumberVerification()
        {
            var from = ballotNumberModifications.LastOrDefault();
            if (from != null)
            {
                return new BallotNumberVerification
                {
                    IgnoreCandidates = from.IgnoreCandidates,
                    NewValue = from.BallotNumber
                };
            }

            return null;
        }

        private IRenumberStrikeoutVerification[] MapRenumberStrikeoutVerifications()
        {
            var modifications = GetLatestRenumberStrikeoutModifications();

            var verifications = new List<IRenumberStrikeoutVerification>();
            foreach (var modification in modifications)
            {
                if (modification is RenumberIsGivenModification)
                {
                    var asRenumber = modification as RenumberIsGivenModification;
                    verifications.Add(new RenumberIsGivenVerification(asRenumber.CandidateIndex, asRenumber.RenumberedTo));
                }
                else if (modification is StrikeoutIsGivenModification)
                {
                    var asStrikeout = modification as StrikeoutIsGivenModification;
                    verifications.Add(new StrikeoutIsGivenVerification(asStrikeout.CandidateIndex));
                }
                else
                {
                    var asNoChange = (NoRenumberOrStrikeoutModification)modification;
                    verifications.Add(new NoRenumberOrStrikeoutVerification(asNoChange.CandidateIndex));
                }
            }

            return verifications.ToArray();
        }

        private IWriteInVerification[] MapWriteInVerifications()
        {
            var modifications = GetLatestWriteInModifications();

            var verifications = new List<IWriteInVerification>();
            foreach (var modification in modifications)
            {
                if (modification is WriteInIsIgnoredModification)
                    verifications.Add(new WriteInIsIgnoredVerification(modification.BallotPaperFieldId));
                else
                {
                    var asVerification = (WriteInIsVerifiedModification)modification;
                    verifications.Add(new WriteInIsVerifiedVerification(asVerification.AffiliationId,
                        asVerification.CandidateName, asVerification.AffiliationShortCode, asVerification.RankInAffiliation, asVerification.BallotPaperFieldId));
                }
            }

            return verifications.ToArray();
        }
    }
}
