using System.Collections.ObjectModel;
using Valg.EVA.Skanning.Contracts.CommonEnums;
using Valg.EVA.Skanning.Entity.SerializedEntities.CountResultModel;
using Valg.EVA.Skanning.ServicesModel.ElectionConfig.Model;
using System.Collections.Generic;
using System.Linq;
using Valg.EVA.Skanning.Oasis.EMLCount;

namespace Valg.EVA.Skanning.Services.CountResult.XmlBuilders
{
    public class CountXmlBuilder : EmlBaseBuilder
    {
        public EML CreateCountResult(ReadOnlyCollection<CastVote> castVotes, string reportingUnitIdentifierOfCaller)
        {
            var emlDocument = CreateEmlCountDocument(castVotes, reportingUnitIdentifierOfCaller);
            AddIndividualCastVoteResults(emlDocument, castVotes);
            return emlDocument;
        }

        private ReportingUnitVotes GetReportingUnitVotesObject(EML emlDocument)
        {
            return emlDocument.Count.Election[0].Contests[0].Items.OfType<ReportingUnitVotes>().First();
        }

        private EML CreateEmlCountDocument(ReadOnlyCollection<CastVote> castVotes, string reportingUnitIdentifierOfCaller)
        {
            ElectionMetaData electionMetaData = GetElectionMetaData(castVotes, reportingUnitIdentifierOfCaller);

            var countMetric = new CountMetricStructure
            {
                Id = electionMetaData.VotingCategory,
                Type = "vote_count_category",
                Value = 1
            };

            var reportingUnitVotes = new ReportingUnitVotes
            {
                
                
                
                
                
                
                ReportingUnitIndicator =  new ReportingUnitIndicatorStructure { Id = electionMetaData.ReportingUnitIdentifier },
                CountMetric = new[] { countMetric }
            };

            var countQualifier = new CountQualifierStructure
            {
                Final = electionMetaData.CountingType == CountingType.Final ? YesNoType.yes : YesNoType.no,
                FinalSpecified = true
            };

            var constest = new CountElectionContest
            {
                ContestIdentifier = new ContestIdentifierStructure { Id = electionMetaData.ContestId },
                CountQualifier = countQualifier,
                Items = new object[] { reportingUnitVotes }
            };

            var electionGroup = new ElectionGroupStructure
            {
                Id = electionMetaData.ElectionGroup
            };

            var election = new CountElection
            {
                ElectionIdentifier = new ElectionIdentifierStructure { Id = electionMetaData.ElectionIdentifier, ElectionGroup = electionGroup },
                Contests = new[] { constest }
            };

            var eventQualifier = new EventQualifierStructure
            {
                Id = electionMetaData.AreaContextId
            };

            var count = new Oasis.EMLCount.Count
            {
                EventIdentifier = new EventIdentifierStructure { Id = electionMetaData.EventIdentifier, EventQualifier = eventQualifier },
                Election = new[] { election }
            };

            return new EML
            {
                Id = "510",
                SchemaVersion = "5.0",
                TransactionId = "1",
                Count = count
            };
        }

        private void AddIndividualCastVoteResults(EML emlDocument, ReadOnlyCollection<CastVote> castVotes)
        {
            var reportingUnitVotes = GetReportingUnitVotesObject(emlDocument);
            reportingUnitVotes.TotalCounted = castVotes.Count.ToString();

            var selections = CreateAffilationSelections(castVotes);
            reportingUnitVotes.Selection = selections.ToArray();

            var rejectedVotes = CreateRejectedVotes(castVotes);
            AddVotesMarkedAsInDoubt(rejectedVotes, castVotes);
            reportingUnitVotes.RejectedVotes = rejectedVotes.ToArray();
        }

        private void AddVotesMarkedAsInDoubt(List<CountElectionContestTotalVotesRejectedVotes> rejectedVotes, ReadOnlyCollection<CastVote> castVotes)
        {
            var numberOfVotesMarkedAsInDoubt = castVotes.Count(p => p.InDoubt);

            if (numberOfVotesMarkedAsInDoubt > 0)
            {
                int totalNumberOfVotes = numberOfVotesMarkedAsInDoubt;

                var suspectRejectionReasonCode = GetSuspectRejectionReasonCode(castVotes);

                CountElectionContestTotalVotesRejectedVotes vaElement = rejectedVotes.FirstOrDefault(p => p.ReasonCode == suspectRejectionReasonCode);

                if (vaElement == null)
                {
                    vaElement = new CountElectionContestTotalVotesRejectedVotes { ReasonCode = suspectRejectionReasonCode };
                    rejectedVotes.Add(vaElement);
                }
                else
                {
                    int existingValue;
                    if (int.TryParse(vaElement.Value, out existingValue))
                    {
                        totalNumberOfVotes += existingValue;
                    }
                }
                vaElement.Value = totalNumberOfVotes.ToString();
            }
        }

        

        private List<CountElectionContestTotalVotesRejectedVotes> CreateRejectedVotes(ReadOnlyCollection<CastVote> castVotes)
        {
            var uniqueListOfRejectionReasons = GetUniqueListOfRejectionReasons(castVotes);

            return uniqueListOfRejectionReasons.Select(rejectionReason => CreateRejectedVotesElement(rejectionReason, castVotes)).ToList();
        }

        private CountElectionContestTotalVotesRejectedVotes CreateRejectedVotesElement(CastVote.RejectionReasonCode rejectionReason, IEnumerable<CastVote> countingResults)
        {
            var numberOfRejectedVotes = countingResults.Count(castVote => castVote.ProposedRejection == rejectionReason);
            return new CountElectionContestTotalVotesRejectedVotes
            {
                ReasonCode = rejectionReason.ToString(),
                Value = numberOfRejectedVotes.ToString()
            };
        }

        private List<CountElectionContestTotalVotesSelection> CreateAffilationSelections(ReadOnlyCollection<CastVote> castVotes)
        {
            var uniqueListOfAffilations = GetUniqueListOfAffilations(castVotes);
            var selections = uniqueListOfAffilations.Select(affilation => CreateSelection(affilation, castVotes)).ToList();
            if (!selections.Any())
            {
                selections.Add(CreateEmptySelection());
            }

            return selections;
        }

        private static CountElectionContestTotalVotesSelection CreateEmptySelection()
        {
            return new CountElectionContestTotalVotesSelection
            {
                Items = new object[]
                {
                    new AffiliationIdentifierStructure
                    {
                        RegisteredName = Affiliation.Blank.RegisteredName,
                        Id = Affiliation.Blank.AffiliationId,
                        ShortCode = Affiliation.Blank.AffiliationShortCode
                    }
                },
                ValidVotes = "0"
            };
        }

        private CountElectionContestTotalVotesSelection CreateSelection(AffiliationIdentifier affilation, IEnumerable<CastVote> countingResults)
        {
            var castVotesInAffiliation = countingResults.Where(p => p.AffiliationIdentifier != null && p.AffiliationIdentifier.AffiliationId == affilation.AffiliationId && p.InDoubt != true).ToArray();
            var numberOfValidVotes = castVotesInAffiliation.Count();
            var numberOfModifiedVotes = castVotesInAffiliation.Count(p => p.IsModified);

            var affiliationIdentifier = new AffiliationIdentifierStructure
            {
                RegisteredName = "",
                Id = affilation.AffiliationId,
                ShortCode = affilation.AffiliationShortCode
            };

            var modifiedVotesCountMetics = new CountMetricStructure
            {
                Type = "modified",
                Value = numberOfModifiedVotes
            };

            return new CountElectionContestTotalVotesSelection
            {
                Items = new object[] { affiliationIdentifier },
                ValidVotes = numberOfValidVotes.ToString(),
                CountMetric = new[] { modifiedVotesCountMetics }
            };
        }

        private IEnumerable<AffiliationIdentifier> GetUniqueListOfAffilations(IEnumerable<CastVote> countingResults)
        {
            var uniqueListOfAffilations = countingResults.Where(p => p.AffiliationIdentifier != null).GroupBy(p => p.AffiliationIdentifier.AffiliationId).Select(group => group.First().AffiliationIdentifier);
            return uniqueListOfAffilations;
        }

        private IEnumerable<CastVote.RejectionReasonCode> GetUniqueListOfRejectionReasons(IEnumerable<CastVote> countingResults)
        {
            var uniqueListOfRejectionReasons = countingResults.Where(p => p.ProposedRejection != CastVote.RejectionReasonCode.NotRejected).GroupBy(p => p.ProposedRejection).Select(group => group.First().ProposedRejection);
            return uniqueListOfRejectionReasons;
        }
    }
}
