using System;
using Valg.EVA.Skanning.Commands;
using Valg.EVA.Skanning.Commands.DeleteCounting;
using Valg.EVA.Skanning.Commands.RegisterBoxAsEmpty;
using Valg.EVA.Skanning.Repositories.Counting;
using Valg.EVA.Skanning.ServicesModel.Internal;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.Box.Write.EmptyBox;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.Count.Read;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.Count.Write;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Valg.EVA.Skanning.Contracts;
using Valg.EVA.Skanning.Contracts.CommonEnums;
using Valg.EVA.Skanning.Commands.DeleteBoxCountingAndRegisterForScanAgain;
using Valg.EVA.Skanning.Commands.ResolveBoxCountingDuplicates;
using Valg.EVA.Skanning.Commands.ResolveLockedBallots;
using Valg.EVA.Skanning.Commands.StartCounting;
using Valg.EVA.Skanning.Diagnostics;
using Valg.EVA.Skanning.Repositories.Box;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.Box.Read;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.Box.Write;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.JobManage.Write;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.Scan.Write;
using BoxCountingDetails = Valg.EVA.Skanning.ServicesModel.ServiceContracts.Count.Read.BoxCountingDetails;
using Counting = Valg.EVA.Skanning.ServicesModel.ServiceContracts.Count.Read.Counting;

namespace Valg.EVA.Skanning.Services.Count
{
    public class CountingService : ICountingService
    {
        private readonly ICommandDispatcher commandDispatcher;
        private readonly ICountingRepository countingRepository;
        private readonly IBoxRepository boxRepository;

        public CountingService(
            ICommandDispatcher commandDispatcher,
            ICountingRepository countingRepository, 
            IBoxRepository boxRepository)
        {
            this.commandDispatcher = commandDispatcher;
            this.countingRepository = countingRepository;
            this.boxRepository = boxRepository;
        }

        private void StartCounting(CountingType countingType, StartCountingParameters parameters)
        {
            string uniqueId = CountingUniqueIdCreator.Create(
                parameters.ElectionEventId,
                parameters.ContestId,
                parameters.EmlAreaContextId,
                parameters.VotingCategory,
                parameters.ElectionIdentifier,
                parameters.ElectionGroup);

            var command = new StartCountingCommand
            {
                CountingType = countingType,
                ElectionEventId = parameters.ElectionEventId,
                ContestId = parameters.ContestId,
                EmlAreaContextId = parameters.EmlAreaContextId,
                CountingEmlAreaContextId = parameters.CountingEmlAreaContextId,
                VotingCategory = parameters.VotingCategory,
                ElectionIdentifier = parameters.ElectionIdentifier,
                ElectionGroup = parameters.ElectionGroup,
                UniqueId = uniqueId,
                CreatedBy = parameters.CreatedBy,
                IsInEmergencyMode = parameters.IsInEmergencyMode
            };

            commandDispatcher.DispatchCommand<StartCountingCommand, int>(command);
        }
       
        public void StartPreliminaryCounting(StartCountingParameters startCountingParameters)
        {
            StartCounting(CountingType.Preliminary, startCountingParameters);
        }

        public void StartFinalCounting(StartCountingParameters startCountingParameters)
        {
            StartCounting(CountingType.Final, startCountingParameters);
        }


        public DeleteCountingResult DeleteCounting(DeleteCountingParameters parameters)
        {
            var command = new DeleteCountingCommand(
                parameters.CountingId,
                parameters.CountingConcurrencyToken,
                parameters.ModifiedBy);

            var commandResult = commandDispatcher.DispatchCommand<DeleteCountingCommand, DeleteCountingCommandResult>(
                command);

            return new DeleteCountingResult(commandResult.DeleteFailed);
        }

        public List<Counting> GetCountings(string electionEventId, string contestId, string countingEmlAreaContextId)
        {
            var countings = countingRepository.GetCountings(electionEventId, contestId, countingEmlAreaContextId);
            return countings.Select(CountingMapper.MapCounting).ToList();
        }

        public Counting GetCountingByBoxCountingId(int boxCountingId)
        {
            var fromCounting = countingRepository.GetCountingByBoxCountingId(boxCountingId);
            return CountingMapper.MapCounting(fromCounting);
        }

        public Counting GetCounting(int countingId)
        {
            var fromCounting = countingRepository.GetCounting(countingId);
            return CountingMapper.MapCounting(fromCounting);
        }

        
        public ReadOnlyCollection<DuplicateBallot> GetDuplicateBallots(int countingId)
        {
            var allDuplicateBallots = new List<DuplicateBallot>();

            var duplicatesInCounting = countingRepository.GetDuplicatesInCounting(countingId);
            foreach (var duplicateBallotId in duplicatesInCounting)
            {
                var ballotsForCurrentBallotId = countingRepository.GetDuplicatedBallotsWithBallotId(duplicateBallotId.BallotId, countingId);
                allDuplicateBallots.AddRange(ballotsForCurrentBallotId.Select(ballot => new DuplicateBallot(duplicateBallotId.BallotId, ballot.Id, ballot.BoxCountingId)));
            }

            return allDuplicateBallots.AsReadOnly();
        }
        
        public RegisterBoxAsEmptyResult RegisterBoxAsEmpty(RegisterBoxAsEmptyParameters parameters)
        {
            var command = new RegisterBoxAsEmptyCommand(
                parameters.BoxCountingId,
                parameters.BoxCountingConcurrencyToken,
                parameters.ModifiedBy,
                parameters.IsInEmergencyMode);

            var commandResult = commandDispatcher.DispatchCommand<RegisterBoxAsEmptyCommand, RegisterBoxAsEmptyCommandResult>(
                command);

            return new RegisterBoxAsEmptyResult(commandResult.RegisterBoxAsEmptyFailed);
        }

        public DeleteBoxCountingAndReregisterForScanningResult DeleteBoxCountingAndReregisterForScanning(
           DeleteBoxCountingAndReregisterForScanningParameters parameters)
        {
            var command = new DeleteBoxCountingAndReregisterForScanningCommand(
                parameters.BarcodeLong,
                parameters.BarcodeShort,
                parameters.CountingId,
                parameters.ScanAgainImmediatly,
                parameters.BoxCountingConcurrencyToken,
                parameters.CountingEmlAreaContextId);

            var commandResult = commandDispatcher.DispatchCommand<DeleteBoxCountingAndReregisterForScanningCommand, DeleteBoxCountingAndReregisterForScanningCommandResult>(
                command);

            return new DeleteBoxCountingAndReregisterForScanningResult(
                commandResult.CountingIsNotActive,
                commandResult.DeleteAndReregisterFailed);
        }

        public List<BoxCountingDetails> GetBoxCountingDetails(string electionEventId, string contestId, string countingEmlAreaContextId)
        {
            var boxCountings = countingRepository.GetBoxCountingDetails(electionEventId, contestId, countingEmlAreaContextId);
            return MapBoxCountingDetails(boxCountings);
        }

        public ResolveLockedBallotsResult ResolveLockedBallots(int boxCountingId, string changedBy, bool isInEmergencyMode)
        {
            var command = new ResolveLockedBallotsCommand(boxCountingId, changedBy, isInEmergencyMode);
            var commandResult = commandDispatcher.DispatchCommand<ResolveLockedBallotsCommand, ResolveLockedBallotsCommandResult>(command);

            var result = new ResolveLockedBallotsResult
            {
                Success = commandResult.Success,
                UnlockedBallotPapers = commandResult.UnlockedBallotPapers
            };
            if (commandResult.Exception != null)
            {
                result.Exception = commandResult.Exception;
            }

            return result;
        }

        public GetLockedBallotsResult GetLockedBallots(int boxCountingId)
        {
            var result = new GetLockedBallotsResult();
            try
            {
                result.LockedBallotPapers = boxRepository.GetLockedBallotPapersInBoxCounting(boxCountingId).Count;
                result.Success = true;
            }
            catch (Exception exception)
            {
                EvaSkanningEventSource.Log.GetLockedBallotsFailed(exception.ToString());
                result.Success = false;
                result.Exception = exception;
            }
            return result;
        }

        private static List<BoxCountingDetails> MapBoxCountingDetails(List<Valg.EVA.Skanning.Entity.ReadModel.BoxCountingDetails> boxCountings)
        {
            return boxCountings.Select(from => new BoxCountingDetails
            {
                Id = from.Id,
                CountingType = from.CountingType,
                ConcurrencyToken = from.Modified,
                CountingId = from.CountingId,
                BarcodeShort = from.BarcodeShort,
                BarcodeLong = from.BarcodeLong,
                BoxCountingState = from.State,
                ScannedBallots = from.ScannedBallots,
                BallotsToVerify = from.BallotsToVerify
            }).ToList();
        }

        public ResolveDuplicatesInCountingResult ResolveDuplicatesInCounting(ResolveDuplicatesInCountingParameters parameters)
        {
            var failureRecorder = ResolveDuplicatesInCountingResult.FailureRecorder();

            try
            {
                foreach (var toDelete in parameters.BoxCountingsToDelete)
                {
                    var command = new DeleteBoxCountingAndReregisterForScanningCommand(toDelete.BarcodeLong, toDelete.BarcodeShort, parameters.CountingId, false, toDelete.ConcurrencyToken, parameters.ReportingUnit);
                    var commandResult = commandDispatcher.DispatchCommand<DeleteBoxCountingAndReregisterForScanningCommand, DeleteBoxCountingAndReregisterForScanningCommandResult>(command);

                    if (commandResult.CountingIsNotActive || commandResult.DeleteAndReregisterFailed)
                    {
                        failureRecorder.BoxCountingCouldNotBeDeleted(toDelete.BarcodeLong);
                    }
                }

                foreach (var toKeep in parameters.BoxCountingsToKeep)
                {
                    var command = new ResolveBoxCountingDuplicatesCommand(toKeep.BarcodeLong, toKeep.BarcodeShort, parameters.CountingId, toKeep.ConcurrencyToken, parameters.ResolvedBy, parameters.IsInEmergencyMode);
                    var commandResult = commandDispatcher.DispatchCommand<ResolveBoxCountingDuplicatesCommand, ResolveBoxCountingDuplicatesCommandResult>(command);
                    if (!commandResult.Success)
                    {
                        failureRecorder.BoxCountingCouldNotBeUpdatedWithNewStatus(toKeep.BarcodeLong, commandResult.Exception);
                    }
                }
            }
            catch (Exception ex)
            {
                EvaSkanningEventSource.Log.DuplicatesResolutionFailed(ex.ToString());
                failureRecorder.ExceptionOccured(ex);
            }

            if (failureRecorder.FailureOccured)
            {
                return failureRecorder.ToResult();
            }

            return ResolveDuplicatesInCountingResult.Success;
        }
    }
}
