using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Valg.EVA.Skanning.Contracts;
using Valg.EVA.Skanning.Identity;
using Valg.EVA.Skanning.Services;
using Valg.EVA.Skanning.Services.Count;
using Valg.EVA.Skanning.Services.TransferCounting;
using Valg.EVA.Skanning.ServicesModel.ElectionConfig.DataProvider;
using Valg.EVA.Skanning.ServicesModel.ElectionConfig.Model;
using Valg.EVA.Skanning.ServicesModel.Internal;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.Count.Read;
using Prism.Events;
using ReactiveUI;
using Valg.EVA.Skanning.Contracts.Security;
using Valg.EVA.Skanning.Diagnostics;
using Valg.EVA.Skanning.JobManagement.Startup.Data.Transfer;

namespace Valg.EVA.Skanning.JobManagement.Startup.Data
{
    public class DataModel : ReactiveObject, IDataModel
    {
        private readonly IElectionConfigDataProvider electionConfigDataProvider;
        private readonly IIdentityProvider identityProvider;
        private readonly ICountingService countingService;
        private readonly ITransferCountingService transferCountingService;
        private readonly ElectionConfigEndpoint electionConfigEndpoint;
        private readonly ITimingProbe timingProbe;
        private readonly IDatabaseConfiguration databaseConfiguration;

        public DataModel(
            IElectionConfigDataProvider electionConfigDataProvider,
            IIdentityProvider identityProvider,
            ICountingService countingService,
            IEventAggregator bus,
            ITransferCountingService transferCountingService, 
            ElectionConfigEndpoint electionConfigEndpoint,
            ITimingProbe timingProbe,
            IDatabaseConfiguration databaseConfiguration)
        {
            this.electionConfigDataProvider = electionConfigDataProvider;
            this.identityProvider = identityProvider;
            this.countingService = countingService;
            this.transferCountingService = transferCountingService;
            this.electionConfigEndpoint = electionConfigEndpoint;
            this.timingProbe = timingProbe;
            this.databaseConfiguration = databaseConfiguration;

            Countings = new ObservableCollection<CountingModel>();
            Transfers = new ObservableCollection<TransferModel>();
            PollingDistricts = new ObservableCollection<PollingDistrictModel>();
            BoxCountings = new ObservableCollection<BoxCountingDetailsModel>();

            bus.GetEvent<SecurityContextHasChangedEvent>()
                .Subscribe(e => LoadData());
        }
        
        public List<VotingCategory> GetValidVotingCategories()
        {
            var votingCategories = new List<VotingCategory>();
            if (identityProvider.CurrentSelectedRole != null)
            {
                var result = electionConfigDataProvider.GetVotingCategoriesInContest(
                    identityProvider.ClaimValue(ClaimDefinitions.ElectionEventIdClaimType), 
                    identityProvider.CurrentSelectedRole.ContestId,
                    identityProvider.CurrentSelectedRole.Area.EmlAreaContextId);
                if (result != null)
                {
                    votingCategories = result;
                }
            }            
            return votingCategories;
        }

        public ObservableCollection<CountingModel> Countings { get; }
        public ObservableCollection<TransferModel> Transfers { get; }
        public ObservableCollection<PollingDistrictModel> PollingDistricts { get; }
        public ObservableCollection<BoxCountingDetailsModel> BoxCountings { get; } 

        public IReactiveDerivedList<CountingCompleteModel> CompletedCountings => Transfers.CreateDerivedCollection(CreateCountingCompleteModel);

        public IReactiveDerivedList<BoxCountingDetailsModel> GetBoxCountings(int countingId)
        {
            return BoxCountings.CreateDerivedCollection(
                m => m, 
                m => m.CountingId == countingId,
                (model, detailsModel) => model.BarcodeLong.GetBoxNumberFromBarcodeLong().CompareTo(detailsModel.BarcodeLong.GetBoxNumberFromBarcodeLong()));
        }

        private CountingCompleteModel CreateCountingCompleteModel(TransferModel transferModel)
        {
            var countingModel = Countings.SingleOrDefault(counting => counting.Id == transferModel.CountingId);
            
            return new CountingCompleteModel(countingModel, transferModel);
        }

        public void LoadData()
        {
            try
            {
                IsLoading = true;
                if (identityProvider.CurrentSelectedRole != null)
                {
                    timingProbe.Measure(() =>
                    {
                        LoadPollingDistricts();
                        LoadCountings();
                        LoadTransfers();
                        LoadBoxCountings();
                    }, () =>
                    {
                        var ev = JobManagementInstrumentedEvents.LoadAllJobManagementData;
                        return new DefaultMeasurementEventData(identityProvider.CurrentSelectedRole.CountingEmlAreaContextId, (int) ev, ev.ToString(), databaseConfiguration.DatabaseServerName);
                    }); 
                }
            }
            catch (Exception ex)
            {
                EvaSkanningEventSource.Log.ErrorWhileLoadingJobbstyringData(ex.ToString());
            }
            finally
            {
                IsLoading = false;
            }
        }

        public bool IsLoading { get; private set; }
      
        private void LoadPollingDistricts()
        {
            if (identityProvider.CurrentSelectedRole != null)
            {
                var electionEventId = identityProvider.ClaimValue(ClaimDefinitions.ElectionEventIdClaimType);
                var contestId = identityProvider.CurrentSelectedRole.ContestId;

                var pollingDistricts = electionConfigEndpoint.ElectionEvent(electionEventId)
                    .Contest(contestId)
                    .PollingDistrict()
                    .Get()
                    .Where(pollingDistrict => pollingDistrict.Id.StartsWith(identityProvider.CurrentSelectedRole.Area.EmlAreaContextId))
                    .Select(pd => new PollingDistrictModel()
                    {
                        Id = pd.Id,
                        Name = pd.Name,
                        VotingCategory = pd.VotingCategory
                    });

                PollingDistricts.Update<PollingDistrictModel, string>(pollingDistricts);
            }
        }

        private void LoadBoxCountings()
        {
            var role = identityProvider.CurrentSelectedRole;
            var boxCountings = countingService.GetBoxCountingDetails(
                identityProvider.ClaimValue(ClaimDefinitions.ElectionEventIdClaimType),
                role.ContestId,
                role.CountingEmlAreaContextId);

            var boxCountingModels = MapBoxCountings(boxCountings).OrderBy(bc => bc.BarcodeLong.GetBoxNumberFromBarcodeLong());

            BoxCountings.Update<BoxCountingDetailsModel, int>(boxCountingModels);
        }

        private static List<BoxCountingDetailsModel> MapBoxCountings(List<BoxCountingDetails> boxCountings)
        {
            return boxCountings.Select(from => new BoxCountingDetailsModel
            {
                BallotsToVerify = from.BallotsToVerify,
                BarcodeLong = from.BarcodeLong,
                BarcodeShort = from.BarcodeShort,
                BoxCountingState = from.BoxCountingState,
                ConcurrencyToken = from.ConcurrencyToken,
                CountingId = from.CountingId,
                CountingType = from.CountingType,
                Id = from.Id,
                ScannedBallots = from.ScannedBallots
            }).ToList();
        }

        public void LoadTransfers()
        {
            var role = identityProvider.CurrentSelectedRole;
            var transfers = transferCountingService.GetTransfers(
                identityProvider.ClaimValue(ClaimDefinitions.ElectionEventIdClaimType),
                role.ContestId,
                role.CountingEmlAreaContextId);

            var mappedTransfers = MapTransfers(transfers);
            
            Transfers.Update<TransferModel, int>(mappedTransfers);
        }

        private IEnumerable<TransferModel> MapTransfers(ReadOnlyCollection<Valg.EVA.Skanning.ServicesModel.ServiceContracts.TransferCounting.Read.Transfer> transfers)
        {
            return transfers.Select(from => new TransferModel
            {
                ErrorCode = from.ErrorCode,
                State = from.State,
                CountingId = from.CountingId,
                Id = from.Id,
                CheckStatusForTransferManually = from.CheckStatusForTransferManually,
                Created = from.Created,
                LatestTransferId = from.LatestTransferId,
                Updated = from.Updated
            }).ToList();
        }

        private void LoadCountings()
        {
            var role = identityProvider.CurrentSelectedRole;
            var countings = countingService.GetCountings(
                identityProvider.ClaimValue(ClaimDefinitions.ElectionEventIdClaimType),
                role.ContestId, 
                role.CountingEmlAreaContextId);
            
            var countingModels = MapCountingModels(countings);

            Countings.Update<CountingModel, int>(countingModels);
        }

        private static List<CountingModel> MapCountingModels(List<Counting> countings)
        {
            return countings.Select(from => new CountingModel
            {
                ConcurrencyToken = from.Modified,
                Id = from.Id,
                CountingType = from.CountingType,
                ContestId = from.ContestId,
                CountNumber = from.CountNumber,
                CountingEmlAreaContextId = from.CountingEmlAreaContextId,
                CountingState = from.CountingState,
                ElectionEventId = from.ElectionEventId,
                ElectionGroup = from.ElectionGroup,
                ElectionIdentifier = from.ElectionIdentifier,
                EmlAreaContextId = from.EmlAreaContextId,
                UniqueId = from.UniqueId,
                VotingCategory = from.VotingCategory
            }).ToList();
        }
    }
}
