using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Payments.Payments.Dtos;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;

namespace EasyAbp.EShop.Payments.Payments
{
    public class PaymentAppService : CrudAppService<Payment, PaymentDto, Guid, PagedAndSortedResultRequestDto, CreatePaymentDto, object>,
        IPaymentAppService
    {
        private readonly IPaymentPayeeAccountProvider _paymentPayeeAccountProvider;
        private readonly IPaymentServiceResolver _paymentServiceResolver;
        private readonly IPaymentRepository _repository;

        public PaymentAppService(
            IPaymentPayeeAccountProvider paymentPayeeAccountProvider,
            IPaymentServiceResolver paymentServiceResolver,
            IPaymentRepository repository) : base(repository)
        {
            _paymentPayeeAccountProvider = paymentPayeeAccountProvider;
            _paymentServiceResolver = paymentServiceResolver;
            _repository = repository;
        }

        public override async Task<PaymentDto> CreateAsync(CreatePaymentDto input)
        {
            await CheckCreatePolicyAsync();

            var providerType = await _paymentServiceResolver.GetProviderTypeOrDefaultAsync(input.PaymentMethod);
            
            var provider = ServiceProvider.GetService(providerType) as IPaymentServiceProvider;

            if (providerType == null || provider == null)
            {
                throw new UnknownPaymentMethodException(input.PaymentMethod);
            }
            
            var paymentItems = input.PaymentItems.Select(inputPaymentItem =>
                new PaymentItem(GuidGenerator.Create(), inputPaymentItem.ItemType, inputPaymentItem.ItemKey,
                    inputPaymentItem.Currency, inputPaymentItem.OriginalPaymentAmount)).ToList();

            if (paymentItems.Select(item => item.Currency).Any(c => c != input.Currency))
            {
                throw new MultiCurrencyNotSupportedException();
            }

            var payment = new Payment(GuidGenerator.Create(), CurrentTenant.Id, input.PaymentMethod, input.Currency,
                paymentItems.Select(item => item.OriginalPaymentAmount).Sum(), paymentItems);

            await Repository.InsertAsync(payment, autoSave: true);

            await CheckPayableAsync(payment, input.ExtraProperties);

            await FillPayeeAccountAsync(payment, input.ExtraProperties);
            
            // Todo: payment discount

            await provider.PayAsync(payment);

            return MapToGetOutputDto(payment);
        }

        protected virtual async Task FillPayeeAccountAsync(Payment payment, Dictionary<string, object> inputExtraProperties)
        {
            payment.SetPayeeAccount(
                await _paymentPayeeAccountProvider.GetPayeeAccountAsync(payment, inputExtraProperties));
        }

        protected virtual async Task CheckPayableAsync(Payment payment, Dictionary<string, object> inputExtraProperties)
        {
            var itemSet = new HashSet<PaymentItem>(payment.PaymentItems);
            
            foreach (var authorizer in ServiceProvider.GetServices<IPaymentAuthorizer>())
            {
                foreach (var item in itemSet.ToList())
                {
                    if (await authorizer.IsPaymentItemAllowedAsync(payment, item, inputExtraProperties))
                    {
                        itemSet.Remove(item);
                    }
                }
            }

            if (!itemSet.IsNullOrEmpty())
            {
                throw new PaymentItemNotPayableException(itemSet.Select(item => item.ItemKey).ToList());
            }
        }

        [RemoteService(false)]
        public override Task<PaymentDto> UpdateAsync(Guid id, object input)
        {
            throw new NotSupportedException();
        }

        [RemoteService(false)]
        public override Task DeleteAsync(Guid id)
        {
            throw new NotSupportedException();
        }
    }
}