using System.Data;
using Valg.EVA.Skanning.Contracts.CommonEnums;
using Valg.EVA.Skanning.Entity.SqlConnection;
using Valg.EVA.Skanning.Repositories.BallotPaper;
using Valg.EVA.Skanning.Repositories.Box;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.Verification.Write;
using System;
using System.Linq;
using Valg.EVA.Skanning.Contracts;
using Valg.EVA.Skanning.Diagnostics;
using Valg.EVA.Skanning.ServicesModel.ElectionConfig.Model;
using Valg.EVA.Skanning.ServicesModel.Internal.BallotNumber;

namespace Valg.EVA.Skanning.Commands.SaveBallotPaperVerifications
{
    public class SaveBallotPaperVerificationsCommandHandler : IHandleCommandsWithReturnValue<SaveBallotPaperVerificationsCommand, SaveBallotPaperVerificationsResult>
    {
        private readonly IBallotPaperRepository ballotPaperRepository;
        private readonly IBoxRepository boxRepository;
        private readonly TransactionScopeFactory transactionScopeFactory;
        private readonly EnsureEmergencyModeFlagFilter ensureEmergencyModeFlagFilter;
        private readonly ResetAllFieldsFilter resetAllFieldsFilter;
        private readonly UpdateBallotCommonFeaturesFilter updateBallotCommonFeaturesFilter;
        private readonly UpdateBallotPersonalVotingFeaturesFilter updateBallotPersonalVotingFeaturesFilter;
        private readonly UpdateBallotAggregatedValuesFilter updateBallotAggregatedValuesFilter;

        public SaveBallotPaperVerificationsCommandHandler(IBallotPaperRepository ballotPaperRepository,
            IBoxRepository boxRepository,
            TransactionScopeFactory transactionScopeFactory,
            EnsureEmergencyModeFlagFilter ensureEmergencyModeFlagFilter,
            ResetAllFieldsFilter resetAllFieldsFilter,
            UpdateBallotCommonFeaturesFilter updateBallotCommonFeaturesFilter,
            UpdateBallotPersonalVotingFeaturesFilter updateBallotPersonalVotingFeaturesFilter,
            UpdateBallotAggregatedValuesFilter updateBallotAggregatedValuesFilter)
        {
            this.ballotPaperRepository = ballotPaperRepository;
            this.boxRepository = boxRepository;
            this.transactionScopeFactory = transactionScopeFactory;
            this.ensureEmergencyModeFlagFilter = ensureEmergencyModeFlagFilter;
            this.resetAllFieldsFilter = resetAllFieldsFilter;
            this.updateBallotCommonFeaturesFilter = updateBallotCommonFeaturesFilter;
            this.updateBallotPersonalVotingFeaturesFilter = updateBallotPersonalVotingFeaturesFilter;
            this.updateBallotAggregatedValuesFilter = updateBallotAggregatedValuesFilter;
        }

        public SaveBallotPaperVerificationsResult CommandResult { get; private set; }

        public void HandleCommand(SaveBallotPaperVerificationsCommand command)
         {
            CommandResult = new SaveBallotPaperVerificationsResult();

            if (BoxCountingStateAllowsSave(command))
            {
                if (CommandAndBallotValuesAllowsSave(command))
                {
                    using (var scope = transactionScopeFactory.GetTransactionScope())
                    {
                        try
                        {
                            ensureEmergencyModeFlagFilter.Execute(command);

                            var currentFieldValues = ballotPaperRepository.GetCurrentFieldValues(command.BallotPaperId);

                            var wasReset = resetAllFieldsFilter.Execute(new ResetAllFieldsFilterInput(command, currentFieldValues));
                            var keepPersonalVotingFeatures = updateBallotCommonFeaturesFilter.Execute(new UpdateBallotCommonFeaturesFilterInput(command, currentFieldValues, !wasReset));
                            updateBallotPersonalVotingFeaturesFilter.Execute(new UpdateBallotPersonalVotingFeaturesFilterInput(command, currentFieldValues, keepPersonalVotingFeatures));

                            var updated = updateBallotAggregatedValuesFilter.Execute(command);

                            if (!updated)
                            {
                                CommandResult.SaveBallotPaperVerificationsOperationResult = SaveBallotPaperVerificationsOperationResult.Incomplete;
                                CommandResult.Success = false;
                            }
                            else
                            {
                                CommandResult.SaveBallotPaperVerificationsOperationResult = SaveBallotPaperVerificationsOperationResult.DataUpdated;
                                CommandResult.Success = true;
                                scope.Complete();
                            }
                        }
                        catch (DBConcurrencyException dbe)
                        {
                            EvaSkanningEventSource.Log.ConcurrencyError(GetType().ToString(), dbe.ToString());
                            CommandResult.Success = false;
                            CommandResult.Exception = dbe;
                            CommandResult.SaveBallotPaperVerificationsOperationResult = SaveBallotPaperVerificationsOperationResult.ConcurrencyError;
                        }
                        catch (Exception e)
                        {
                            EvaSkanningEventSource.Log.SaveBallotVerificationsFailed(e.ToString());
                            CommandResult.Success = false;
                            CommandResult.Exception = e;
                        }
                    }
                }
            }
            else
            {
                CommandResult.SaveBallotPaperVerificationsOperationResult = SaveBallotPaperVerificationsOperationResult.NotReadyForVerify;
            }
        }

        private bool CommandAndBallotValuesAllowsSave(SaveBallotPaperVerificationsCommand command)
        {
            var commandAndBallotValuesAllowsSave = false;

            if (command.IsEmpty)
            {
                var ballotPaper = ballotPaperRepository.GetBallotPaper(command.BallotPaperId);
                if (ballotPaper.IsValid)
                {
                    CommandResult.SaveBallotPaperVerificationsOperationResult = SaveBallotPaperVerificationsOperationResult.NoChanges;
                    CommandResult.Success = true;
                }
                else
                { 
                    var fieldValues = ballotPaperRepository.GetCurrentFieldValues(command.BallotPaperId);
                    var ballotNoField = fieldValues.SingleOrDefault(f => f.IsBallotNoField());
                    if (ballotNoField != null
                        && !ballotNoField.NeedsVerification
                        && Affiliation.IsProposedShortCodeBlankAffiliation(ballotNoField.Value.AffiliationShortCode()))
                    {
                        
                        commandAndBallotValuesAllowsSave = true;
                    }
                    else
                    {
                        throw new InvalidOperationException(
                            $"Saving a invalid ballot and not specifiying any verified fields is not allowed. This is an indication of a programming error. BallotPaperId = {command.BallotPaperId}");
                    }
                }
            }
            else
            {
                commandAndBallotValuesAllowsSave = true;
            }

            return commandAndBallotValuesAllowsSave;
        }

        private bool BoxCountingStateAllowsSave(SaveBallotPaperVerificationsCommand command)
        {
            return boxRepository.GetBoxCountingStateByBallotPaperId(command.BallotPaperId) == BoxCountingState.ReadyForVerify;
        }
    }
}
