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 Valg.EVA.Skanning.ServicesModel.ServiceContracts.CountResult.Write;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.Scan.Write;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.TransferCounting.Write;
using Valg.EVA.Skanning.UserInterfaceElements;
using Valg.EVA.Skanning.Identity;
using Valg.EVA.Skanning.Services.Count;
using Valg.EVA.Skanning.Services.CountResult;
using Valg.EVA.Skanning.Services.TransferCounting;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Threading;
using Prism.Events;
using Valg.EVA.Skanning.Contracts;
using Valg.EVA.Skanning.Contracts.Security;
using Valg.EVA.Skanning.Diagnostics;
using Valg.EVA.Skanning.UserInterfaceElements.ErrorHandling;
using Valg.EVA.Skanning.JobManagement.Startup.Actions.Counting;
using Valg.EVA.Skanning.JobManagement.Startup.Data;
using Valg.EVA.Skanning.JobManagement.Startup.DataRefresh;
using Valg.EVA.Skanning.JobManagement.Startup.UI.Counting;
using Valg.EVA.Skanning.JobManagement.Startup.UI.Signing;

namespace Valg.EVA.Skanning.JobManagement.Startup
{
    public class JobManagementApplication : IJobManagementApplication
    {
        private readonly IIdentityProvider identityProvider;
        private readonly IDataModel dataModel;
        private readonly ICountingResultService countingResultService;
        private readonly ITransferCountingService transferService;
        private readonly ISigningErrors signingErrors;
        private readonly ICountingPopups countingPopups;
        private readonly ITransferErrorsPresenter transferErrorsPresenter;
        private readonly ICountingService countingService;
        private readonly IBuypassBootstrapper buypassBootstrapper;
        private readonly IWaitCursorProvider waitCursorProvider;
        private readonly ITimingProbe timingProbe;
        private readonly IDatabaseConfiguration databaseConfiguration;

        public JobManagementApplication(
            IIdentityProvider identityProvider,
            IDataModel dataModel,
            IEventAggregator eventAggregator,
            ICountingResultService countingResultService,
            ITransferCountingService transferService,
            ICountingService countingService,
            ISigningErrors signingErrors,
            ICountingPopups countingPopups,
            ITransferErrorsPresenter transferErrorsPresenter,
            IBuypassBootstrapper buypassBootstrapper,
            IWaitCursorProvider waitCursorProvider,
            ITimingProbe timingProbe,
            IDatabaseConfiguration databaseConfiguration)
        {
            this.identityProvider = identityProvider;
            this.dataModel = dataModel;
            this.countingResultService = countingResultService;
            this.transferService = transferService;
            this.countingService = countingService;
            this.signingErrors = signingErrors;
            this.countingPopups = countingPopups;
            this.transferErrorsPresenter = transferErrorsPresenter;
            this.buypassBootstrapper = buypassBootstrapper;
            this.waitCursorProvider = waitCursorProvider;
            this.timingProbe = timingProbe;
            this.databaseConfiguration = databaseConfiguration;

            eventAggregator.GetEvent<DataRefreshRequestedEvent>().Subscribe(RefreshData);
        }

        private void RefreshData(DataRefreshRequestedEventArgs dataRefreshRequestedEventArgs)
        {
            if (!dataModel.IsLoading)
            {
                if (dataRefreshRequestedEventArgs.DataToRefresh == DataRefreshType.All)
                {
                    dataModel.LoadData();
                }
                else if (dataRefreshRequestedEventArgs.DataToRefresh == DataRefreshType.TransferDataOnly)
                {
                    dataModel.LoadTransfers();
                }
            }
        }

        public void StartFinalCounting(VotingCategoryType votingCategoryType, PollingDistrictModel pollingDistrict)
        {
            var action = new StartCountingAction(countingService, identityProvider);
            action.StartFinalCounting(votingCategoryType, pollingDistrict);
            LoadCountingAndTransfersInDatamodel();
        }

        public void StartPreliminaryCounting(VotingCategoryType votingCategoryType, PollingDistrictModel pollingDistrict)
        {
            var action = new StartCountingAction(countingService, identityProvider);
            action.StartPreliminaryCounting(votingCategoryType, pollingDistrict);
            LoadCountingAndTransfersInDatamodel();
        }

        public GenerateCountingResultResult CreateResult(CountingModel model)
        {
            return countingResultService.GenerateCountingResult(CreateServiceParameters(model));
        }

        public void LoadCountingAndTransfersInDatamodel()
        {
            dataModel.LoadData();
        }

        private GenerateCountingResultParameters CreateServiceParameters(CountingModel model)
        {
            var boxCountings = dataModel.GetBoxCountings(model.Id);
            var boxCountingsInMemory = boxCountings.Select(b => new BoxCountingToLock(b.Id, b.ConcurrencyToken, b.BarcodeLong, b.BarcodeShort, b.BoxCountingState)).ToList();

            return new GenerateCountingResultParameters(
                identityProvider.GetCurrentUserName(),
                identityProvider.CurrentSelectedRole.ReportingUnitIdentifier,
                model.Id,
                model.ConcurrencyToken,
                boxCountingsInMemory,
                identityProvider.IsInEmergencyMode);
        }

        public byte[] CreateResultFile(int countingId)
        {
            buypassBootstrapper.AttemptRestartBuypassMiddleware();

            GenerateCountingEmlResult result;
            using (waitCursorProvider.GetWaitCursor())
            {
                var parameters = new GenerateCountingEmlParameters(identityProvider.CurrentSelectedRole.ReportingUnitIdentifier, countingId);
                result = countingResultService.GetCountingAsEml(parameters);
            }

            if (!result.SigningResult.IsSigningSuccessful)
            {
                if (result.SigningResult.IsSigningCertificateMissing)
                {
                    signingErrors.ShowCertificateMissingInformation();
                }
            }

            return result.ZipFile;
        }

        public async Task TransferResultFileAsync(int countingId, int transferJobId)
        {
            var resultArchive = CreateResultFile(countingId);
            if (resultArchive != null)
            {
                var userName = identityProvider.GetCurrentUserName();
                var role = identityProvider.CurrentSelectedRole;

                var saveEvent = JobManagementInstrumentedEvents.SaveResultFileBeforeTransfer;
                int transferId = 0;
                await timingProbe.MeasureAsync(async ()=> transferId = await transferService.SaveResultFileForTransferAsync(transferJobId, userName, resultArchive), 
                    () => new DefaultMeasurementEventData(role.CountingEmlAreaContextId,(int) saveEvent, saveEvent.ToString(), databaseConfiguration.DatabaseServerName));
                
                TransferUpdated();

                var parameters = new TransferCountingParameters(transferId)
                {
                    Token = identityProvider.Token,
                    TokenSignature = identityProvider.TokenSignature,
                    AreaPath = role.TransferAreaContextId,
                    ElectionPath = role.Area.ElectionEventId,
                    RoleId = role.RoleId,
                    UserId = identityProvider.ClaimValue(ClaimDefinitions.TokenUidClaimType),
                    UserName = userName,
                    ResultArchive = resultArchive,
                };

                TransferCountingResult result = null;

                var transferEvent = JobManagementInstrumentedEvents.TransferResultFile;
                await timingProbe.MeasureAsync(async () => result = await transferService.TransferCountingAsync(parameters), 
                    () => new DefaultMeasurementEventData(role.CountingEmlAreaContextId, (int) transferEvent, transferEvent.ToString(), databaseConfiguration.DatabaseServerName));
                
                if (!result.Success)
                {
                    transferErrorsPresenter.ShowError(result.FailureDetails);
                }

                TransferUpdated();
            }
        }

        private void TransferUpdated()
        {
            Dispatcher.CurrentDispatcher.BeginInvoke(new Action(dataModel.LoadTransfers));
        }

        public void ClearContentOfBoxCounting(BoxCountingDetailsModel boxCountingDetailsModel)
        {
            var parameters = new DeleteBoxCountingAndReregisterForScanningParameters(
                boxCountingDetailsModel.BarcodeLong,
                boxCountingDetailsModel.BarcodeShort,
                boxCountingDetailsModel.CountingId,
                false,
                boxCountingDetailsModel.ConcurrencyToken,
                identityProvider.CurrentSelectedRole.CountingEmlAreaContextId,
                identityProvider.GetCurrentUserName());

            var result = countingService.DeleteBoxCountingAndReregisterForScanning(parameters);

            if (result.DeleteAndReregisterFailed)
            {
                countingPopups.ShowBoxCouldNotBeResetInformation();
            }

            RefreshData(new DataRefreshRequestedEventArgs());
        }

        public void DeleteCounting(CountingModel model)
        {
            var parameters = new DeleteCountingParameters(
                model.Id,
                model.ConcurrencyToken,
                identityProvider.GetCurrentUserName());

            var result = countingService.DeleteCounting(parameters);

            if (result.DeleteFailed)
            {
                countingPopups.ShowCountingCouldNotBeDeletedInformation();
            }

            RefreshData(new DataRefreshRequestedEventArgs());
        }

        public void ResolveLockedBallots(BoxCountingDetailsModel boxCountingDetailsModel)
        {
            var result = countingService.ResolveLockedBallots(
                boxCountingDetailsModel.Id,
                identityProvider.GetCurrentUserName(),
                identityProvider.IsInEmergencyMode);

            if (result.Success)
                countingPopups.ShowBallotsUnlockedInformation(result.UnlockedBallotPapers);
            else
                ApplicationUnhandledExceptionHandler.LogErrorAndShowMessageBox(result.Exception);
        }

        public int GetLockedBallotsForBoxCounting(int boxCountingId)
        {
            var result = countingService.GetLockedBallots(boxCountingId);
            if (!result.Success)
            {
                ApplicationUnhandledExceptionHandler.LogErrorAndShowMessageBox(result.Exception);
                return 0;
            }

            return result.LockedBallotPapers;
        }

        public ReadOnlyCollection<DuplicateBallot> GetDuplicateBallots(int countingId)
        {
            return countingService.GetDuplicateBallots(countingId);
        }

        public ResolveDuplicatesInCountingResult ResolveDuplicates(int countingId, string reportingUnit, BoxCountingToResolve[] boxCountingsToDelete, BoxCountingToResolve[] boxCountingsToKeep)
        {
            return countingService.ResolveDuplicatesInCounting(new ResolveDuplicatesInCountingParameters(countingId, reportingUnit, boxCountingsToDelete, boxCountingsToKeep,
                identityProvider.GetCurrentUserName(), identityProvider.IsInEmergencyMode));
        }

        public void SetBoxAsEmpty(BoxCountingDetailsModel boxCountingDetailsModel)
        {
            var parameters = new RegisterBoxAsEmptyParameters(
                boxCountingDetailsModel.Id,
                boxCountingDetailsModel.ConcurrencyToken,
                identityProvider.GetCurrentUserName(),
                identityProvider.IsInEmergencyMode);

            var result = countingService.RegisterBoxAsEmpty(parameters);

            if (result.RegisterBoxAsEmptyFailed)
            {
                countingPopups.ShowBoxCouldNotBeSetToEmptyInformation();
            }

            RefreshData(new DataRefreshRequestedEventArgs());
        }
    }
}

