using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.ServiceModel;
using System.Threading.Tasks;
using Valg.EVA.Skanning.Commands;
using Valg.EVA.Skanning.Commands.RefreshUpdatedTimestampOnTransfer;
using Valg.EVA.Skanning.Commands.SaveResultFileForTransfer;
using Valg.EVA.Skanning.Commands.SetTransferFailed;
using Valg.EVA.Skanning.Commands.SetTransferSuccessful;
using Valg.EVA.Skanning.Configuration;
using Valg.EVA.Skanning.Contracts;
using Valg.EVA.Skanning.Contracts.CommonEnums;
using Valg.EVA.Skanning.Diagnostics;
using Valg.EVA.Skanning.Repositories.Transfers;
using Valg.EVA.Skanning.ServicesModel.Internal.UploadWebService;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.TransferCounting.Read;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.TransferCounting.Write;
using Valg.EVA.Skanning.Tools;

namespace Valg.EVA.Skanning.Services.TransferCounting
{
    public class TransferCountingService : ITransferCountingService
    {
        private readonly IUploadWebServiceFactory uploadWebServiceFactory;
        private readonly ITransferRepository transferRepository;
        private readonly IExternalEndpointsConfiguration externalEndpointsConfiguration;
        private readonly ICommandDispatcher commandDispatcher;

        public TransferCountingService(IUploadWebServiceFactory uploadWebServiceFactory,
            ITransferRepository transferRepository,
            IExternalEndpointsConfiguration externalEndpointsConfiguration,
            ICommandDispatcher commandDispatcher)
        {
            this.uploadWebServiceFactory = uploadWebServiceFactory;
            this.transferRepository = transferRepository;
            this.externalEndpointsConfiguration = externalEndpointsConfiguration;
            this.commandDispatcher = commandDispatcher;


            
            System.Net.ServicePointManager.Expect100Continue = false;
        }

        public async Task<TransferCountingResult> TransferCountingAsync(TransferCountingParameters parameters)
        {
            var transferId = parameters.TransferId;

            var result = TransferCountingResult.SuccessResult();
            long? fileId = null;

            try
            {
                var tokenArchive = Zip.Create(new Dictionary<string, byte[]>
                {
                    {"token.xml", parameters.Token},
                    {"token.pem", parameters.TokenSignature}
                });

                using (var service = uploadWebServiceFactory.CreateTransferClient())
                {
                    var uploadResult = await service.Operations.uploadFileAsync(GetUploadFileRequest(parameters, tokenArchive)).ConfigureAwait(false);
                    fileId = uploadResult.fileId;
                }
            }
            catch (FaultException<evoteWsFault> ex)
            {
                var faultInformation = ex.Detail;
                result = TransferCountingResult.FailureWithErrorCodeResult(transferId, faultInformation.faultCode, faultInformation.faultString);
            }
            catch (CommunicationException communicationException)
            {
                
                result = TransferCountingResult.FailureWithTimeoutExceptionResult(transferId, communicationException);
            }
            catch (TimeoutException timeoutException)
            {
                
                result = TransferCountingResult.FailureWithTimeoutExceptionResult(transferId, timeoutException);
            }
            catch (Exception ex)
            {
                result = TransferCountingResult.FailureWithUnhandledExceptionResult(transferId, ex);
            }

            if (result.Success && fileId.HasValue)
            {
                EvaSkanningEventSource.Log.TransferOfCountingSucceeded(transferId, fileId.Value, externalEndpointsConfiguration.TransferWebServiceUrl);
                await SetTransferSuccessfulAsync(transferId, parameters.UserName, fileId.Value).ConfigureAwait(false);
            }
            else
            {
                var exception = "No exception";
                if (result.FailureDetails.Exception != null)
                {
                    exception = result.FailureDetails.Exception.ToString();
                }

                EvaSkanningEventSource.Log.TransferOfCountingFailed(transferId, 
                    result.FailureDetails.Message, 
                    result.FailureDetails.ErrorCode, 
                    result.FailureDetails.ErrorString,
                    exception,
                    externalEndpointsConfiguration.TransferWebServiceUrl);

                await WriteErrorInformationToDatabase(result, parameters.UserName).ConfigureAwait(false);
            }

            return result;
        }

        private async Task SetTransferSuccessfulAsync(int transferId, string userName, long fileId)
        {
            var command = new SetTransferSuccessfulCommand(transferId, userName, fileId);
            await commandDispatcher.DispatchCommandAsync(command).ConfigureAwait(false);
        }

        private async Task WriteErrorInformationToDatabase(TransferCountingResult result, string updatedBy)
        {
            var command = new SetTransferFailedCommand(result.FailureDetails.TransferId, result.FailureDetails.ErrorCode, updatedBy);
            await commandDispatcher.DispatchCommandAsync(command).ConfigureAwait(false);
        }

        private static uploadFile GetUploadFileRequest(TransferCountingParameters parameters, byte[] tokenArchive)
        {
            return new uploadFile(
                tokenArchive,
                parameters.UserId,
                parameters.RoleId,
                parameters.AreaPath,
                parameters.ElectionPath,
                parameters.ResultArchive,
                "e.count.upload");
        }
        
        public ReadOnlyCollection<Transfer> GetTransfers(string electionEventId, string contestId, string countingEmlAreaContextId)
        {
            var transfers = transferRepository.GetTransfers(electionEventId, contestId, countingEmlAreaContextId);
            return transfers.Select(repoTrans => new Transfer
            {
                CheckStatusForTransferManually = MapCheckForTransferManually(repoTrans),
                CountingId = repoTrans.CountingId,
                Created = repoTrans.Created,
                Updated = repoTrans.Updated,
                FileId = repoTrans.FileId,
                Id = repoTrans.Id,
                ErrorCode = repoTrans.ErrorCode,
                State = repoTrans.State,
                LatestTransferId = repoTrans.LatestTransferId
            }).ToList().AsReadOnly();
        }

        private bool MapCheckForTransferManually(Valg.EVA.Skanning.Entity.ReadModel.Transfer repoTrans)
        {
            if (repoTrans.Updated.HasValue)
            {
                var expirationInMinutes = externalEndpointsConfiguration.StatusCheckExpirationInMinutes;
                return DateTime.Now.Subtract(repoTrans.Updated.Value).Minutes >= expirationInMinutes
                       && (repoTrans.State.Equals(TransferState.FileTransferFinished) || repoTrans.State.Equals(TransferState.QueuedOnServer) || repoTrans.State.Equals(TransferState.StartedOnServer));
            }

            return false;
        }

        public TransferDetails GetTransferDetails(int countingId)
        {
            var transfer = transferRepository.GetTransferDetails(countingId);
            return new TransferDetails
            {
                CountingId = transfer.CountingId,
                Id = transfer.Id,
                RejectedVotes = transfer.RejectedVotes,
                ErrorCode = transfer.ErrorCode,
                State = transfer.State,
                TotalVotes = transfer.TotalVotes,
                VerifiedVotes = transfer.VerifiedVotes
            };
        }

        public async Task<int> SaveResultFileForTransferAsync(int transferJobId, string userName, byte[] resultArchive)
        {
            var command = new SaveResultFileForTransferCommand(transferJobId, userName, resultArchive);
            var result = await commandDispatcher.DispatchCommandAsync<SaveResultFileForTransferCommand, int>(command).ConfigureAwait(false);
            return result;
        }

        public async Task RefreshUpdatedTimestampOnTransferAsync(int transferId, string userName, DateTime lastUpdated)
        {
            var command = new RefreshUpdatedTimestampOnTransferCommand(transferId, userName, lastUpdated);
            await commandDispatcher.DispatchCommandAsync(command).ConfigureAwait(false);
        }
    }
}

