using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Valg.EVA.Skanning.Repositories.ElectionConfig;
using Valg.EVA.Skanning.ServicesModel.Internal;
using Valg.EVA.Skanning.ServicesModel.ElectionConfig.Model;
using Valg.EVA.Skanning.ServicesModel.Internal.BallotNumber;

namespace Valg.EVA.Skanning.ServicesModel.ElectionConfig.DataProvider
{
    public class CandidateListDataProvider : ICandidateListDataProvider
    {
        private readonly IAffiliationMapper affiliationMapper;
        private readonly ICandidateListRepository candidateListRepository;
        private readonly IElectionConfigXmlDeserializationHelper deserializationHelper;
        private readonly ConcurrentDictionary<string, ILookup<string, Affiliation>> affiliationsByContestId = new ConcurrentDictionary<string, ILookup<string, Affiliation>>();
        private readonly ConcurrentDictionary<string, List<Candidate>> candidateList = new ConcurrentDictionary<string, List<Candidate>>();
        private readonly ConcurrentDictionary<string, IList<PollingDistrict>> pollingDistricts = new ConcurrentDictionary<string, IList<PollingDistrict>>();

        public CandidateListDataProvider(IAffiliationMapper affiliationMapper, ICandidateListRepository candidateListRepository, IElectionConfigXmlDeserializationHelper deserializationHelper)
        {
            this.affiliationMapper = affiliationMapper;
            this.candidateListRepository = candidateListRepository;
            this.deserializationHelper = deserializationHelper;
        }

        public Affiliation GetAffiliation(string electionEventId, string contestIdentifier, string affiliationShortCode)
        {
            if (Affiliation.IsProposedShortCodeBlankAffiliation(affiliationShortCode))
            {
                return Affiliation.Blank;
            }
            return GetAffiliations(electionEventId, contestIdentifier)
                .SingleOrDefault(a => a.AffiliationShortCode == affiliationShortCode);
        }

        public List<Affiliation> GetAffiliations(string electionEventId, string contestIdentifier)
        {
            return affiliationsByContestId.GetOrAdd(CreateCachingId(electionEventId, contestIdentifier), eEId => LoadAffiliationsForContestId(electionEventId, contestIdentifier))[contestIdentifier].ToList();
        }

        public List<Candidate> GetWriteInCandidates(string electionEventId, string contestIdentifier, string affiliationShortCode)
        {
            var cachingId = CreateCachingId(electionEventId, contestIdentifier);
            var allCandidates = candidateList.GetOrAdd(cachingId, key => LoadCandidates(electionEventId, contestIdentifier));
            
            return allCandidates
                .Where(c => c.AffiliationShortCode != affiliationShortCode)
                .ToList();
        }

        public List<PollingDistrict> GetPollingDistricts(string electionEventId)
        {
            if (electionEventId.IsStringActuallyLongOrInt())
            {
                return pollingDistricts.GetOrAdd(electionEventId, LoadPollingDistrictData).ToList();
            }

            return null;
        }

        public PollingDistrict GetPollingDistrict(string electionEventId, string pollingDistrictId)
        {
            if (electionEventId.IsStringActuallyLongOrInt() && pollingDistrictId.IsStringActuallyLongOrInt())
            {
                var idEmlFormat = pollingDistrictId.GetPollingDistrictIdEmlFormat();

                return pollingDistricts.GetOrAdd(electionEventId, LoadPollingDistrictData)
                    .FirstOrDefault(x => x.Id == idEmlFormat);
            }

            return null;
        }

        private string CreateCachingId(string electionEventId, string contestIdentifier)
        {
            return electionEventId + contestIdentifier;
        }

        private ILookup<string, Affiliation> LoadAffiliationsForContestId(string electionEventId, string contestIdentifier)
        {
            var affilationsXml = candidateListRepository.GetAffiliations(electionEventId, contestIdentifier)
                .Select(a => a.XmlContent)
                .ToList();

            return affiliationMapper.MapAffiliations(affilationsXml).ToLookup(a => contestIdentifier);
        }

        private List<Candidate> LoadCandidates(string electionEventId, string contestIdentifier)
        {
            var candidates = new List<Candidate>();
            var affiliations = GetAffiliations(electionEventId, contestIdentifier);
            if (affiliations != null)
            {
                foreach (var affiliation in affiliations)
                {
                    foreach (var candidate in affiliation.Candidates)
                    {
                        candidate.AffiliationShortCode = affiliation.AffiliationShortCode;
                        candidate.AffiliationId = affiliation.AffiliationId;
                        candidates.Add(candidate);
                    }
                }
            }
            return candidates;
        }

        private List<PollingDistrict> LoadPollingDistrictData(string electionEventId)
        {
            var xmlResults = candidateListRepository.GetPollingDistricts(electionEventId);

            return deserializationHelper.DeserializePollingDistricts(xmlResults);
        }
    }
}
