using Grand.Business.Core.Interfaces.Catalog.Prices;
using Grand.Business.Core.Interfaces.Catalog.Tax;
using Grand.Business.Core.Interfaces.Checkout.Shipping;
using Grand.Business.Core.Interfaces.Common.Directory;
using Grand.Business.Core.Interfaces.Common.Localization;
using Grand.Business.Core.Interfaces.Common.Security;
using Grand.Domain.Common;
using Grand.Domain.Shipping;
using Grand.Web.Features.Models.Checkout;
using Grand.Web.Features.Models.Common;
using Grand.Web.Models.Checkout;
using MediatR;

namespace Grand.Web.Features.Handlers.Checkout;

public class GetShippingAddressHandler : IRequestHandler<GetShippingAddress, CheckoutShippingAddressModel>
{
    private readonly IAclService _aclService;
    private readonly AddressSettings _addressSettings;
    private readonly ICountryService _countryService;
    private readonly ICurrencyService _currencyService;
    private readonly IMediator _mediator;
    private readonly IPickupPointService _pickupPointService;
    private readonly IPriceFormatter _priceFormatter;
    private readonly IShippingService _shippingService;
    private readonly ShippingSettings _shippingSettings;
    private readonly ITaxService _taxService;
    private readonly ITranslationService _translationService;

    public GetShippingAddressHandler(
        IShippingService shippingService,
        IPickupPointService pickupPointService,
        ITaxService taxService,
        ICurrencyService currencyService,
        IPriceFormatter priceFormatter,
        ITranslationService translationService,
        ICountryService countryService,
        IAclService aclService,
        IMediator mediator,
        ShippingSettings shippingSettings,
        AddressSettings addressSettings)
    {
        _shippingService = shippingService;
        _pickupPointService = pickupPointService;
        _taxService = taxService;
        _currencyService = currencyService;
        _priceFormatter = priceFormatter;
        _translationService = translationService;
        _countryService = countryService;
        _aclService = aclService;
        _mediator = mediator;
        _shippingSettings = shippingSettings;
        _addressSettings = addressSettings;
    }

    public async Task<CheckoutShippingAddressModel> Handle(GetShippingAddress request,
        CancellationToken cancellationToken)
    {
        var model = new CheckoutShippingAddressModel {
            BillToTheSameAddress = true,
            //allow pickup in store?
            AllowPickUpInStore = _shippingSettings.AllowPickUpInStore
        };
        if (model.AllowPickUpInStore) await PreparePickupPoints(model, request);

        await PrepareAddresses(model, request);

        //new address
        model.ShippingNewAddress.CountryId = request.SelectedCountryId;
        var countries = await _countryService.GetAllCountriesForShipping(request.Language.Id, request.Store.Id);
        model.ShippingNewAddress = await _mediator.Send(new GetAddressModel {
            Language = request.Language,
            Store = request.Store,
            Model = model.ShippingNewAddress,
            Address = null,
            ExcludeProperties = false,
            LoadCountries = () => countries,
            PrePopulateWithCustomerFields = request.PrePopulateNewAddressWithCustomerFields,
            Customer = request.Customer,
            OverrideAttributes = request.OverrideAttributes
        }, cancellationToken);
        model.ShippingNewAddress.HideAddressType = true;
        model.ShippingNewAddress.AddressTypeId =
            _addressSettings.AddressTypeEnabled ? (int)AddressType.Shipping : (int)AddressType.Any;

        return model;
    }

    private async Task PreparePickupPoints(CheckoutShippingAddressModel model, GetShippingAddress request)
    {
        var pickupPoints = await _pickupPointService.LoadActivePickupPoints(request.Store.Id);

        if (pickupPoints.Any())
            foreach (var pickupPoint in pickupPoints)
            {
                var pickupPointModel = new CheckoutPickupPointModel {
                    Id = pickupPoint.Id,
                    Name = pickupPoint.Name,
                    Description = pickupPoint.Description,
                    Address = pickupPoint.Address
                };
                if (pickupPoint.PickupFee > 0)
                {
                    var amount = (await _taxService.GetShippingPrice(pickupPoint.PickupFee, request.Customer))
                        .shippingPrice;
                    amount = await _currencyService.ConvertFromPrimaryStoreCurrency(amount, request.Currency);
                    pickupPointModel.PickupFee = _priceFormatter.FormatPrice(amount, request.Currency);
                }

                model.PickupPoints.Add(pickupPointModel);
            }

        if (!(await _shippingService.LoadActiveShippingRateCalculationProviders(request.Customer, request.Store.Id))
            .Any())
        {
            if (!pickupPoints.Any())
            {
                model.Warnings.Add(_translationService.GetResource("Checkout.ShippingIsNotAllowed"));
                model.Warnings.Add(_translationService.GetResource("Checkout.PickupPoints.NotAvailable"));
            }

            model.PickUpInStoreOnly = true;
            model.PickUpInStore = true;
        }
    }

    private async Task PrepareAddresses(CheckoutShippingAddressModel model, GetShippingAddress request)
    {
        //existing addresses
        var addresses = new List<Address>();
        foreach (var item in request.Customer.Addresses.Where(x =>
                     x.AddressType is AddressType.Any or AddressType.Shipping))
        {
            if (string.IsNullOrEmpty(item.CountryId))
            {
                addresses.Add(item);
                continue;
            }

            var country = await _countryService.GetCountryById(item.CountryId);
            if (country == null || (country.AllowsShipping && _aclService.Authorize(country, request.Store.Id)))
                addresses.Add(item);
        }

        foreach (var address in addresses)
        {
            var addressModel = await _mediator.Send(new GetAddressModel {
                Language = request.Language,
                Store = request.Store,
                Model = null,
                Address = address,
                ExcludeProperties = false
            });
            model.ExistingAddresses.Add(addressModel);
        }
    }
}