using Nop.Core.Domain.Affiliates;
using Nop.Services.Affiliates;
using Nop.Services.Catalog;
using Nop.Services.Common;
using Nop.Services.Customers;
using Nop.Services.Directory;
using Nop.Services.Helpers;
using Nop.Services.Localization;
using Nop.Services.Orders;
using Nop.Web.Areas.Admin.Infrastructure.Mapper.Extensions;
using Nop.Web.Areas.Admin.Models.Affiliates;
using Nop.Web.Areas.Admin.Models.Common;
using Nop.Web.Framework.Models.Extensions;

namespace Nop.Web.Areas.Admin.Factories;

/// <summary>
/// Represents the affiliate model factory implementation
/// </summary>
public partial class AffiliateModelFactory : IAffiliateModelFactory
{
    #region Fields

    protected readonly IAddressModelFactory _addressModelFactory;
    protected readonly IAddressService _addressService;
    protected readonly IAffiliateService _affiliateService;
    protected readonly IBaseAdminModelFactory _baseAdminModelFactory;
    protected readonly ICountryService _countryService;
    protected readonly ICustomerService _customerService;
    protected readonly IDateTimeHelper _dateTimeHelper;
    protected readonly ILocalizationService _localizationService;
    protected readonly IOrderService _orderService;
    protected readonly IPriceFormatter _priceFormatter;
    protected readonly IStateProvinceService _stateProvinceService;

    #endregion

    #region Ctor

    public AffiliateModelFactory(IAddressModelFactory addressModelFactory,
        IAddressService addressService,
        IAffiliateService affiliateService,
        IBaseAdminModelFactory baseAdminModelFactory,
        ICountryService countryService,
        ICustomerService customerService,
        IDateTimeHelper dateTimeHelper,
        ILocalizationService localizationService,
        IOrderService orderService,
        IPriceFormatter priceFormatter,
        IStateProvinceService stateProvinceService)
    {
        _addressModelFactory = addressModelFactory;
        _addressService = addressService;
        _affiliateService = affiliateService;
        _baseAdminModelFactory = baseAdminModelFactory;
        _countryService = countryService;
        _customerService = customerService;
        _dateTimeHelper = dateTimeHelper;
        _localizationService = localizationService;
        _orderService = orderService;
        _priceFormatter = priceFormatter;
        _stateProvinceService = stateProvinceService;
    }

    #endregion

    #region Utilities

    /// <summary>
    /// Prepare affiliated order search model
    /// </summary>
    /// <param name="searchModel">Affiliated order search model</param>
    /// <param name="affiliate">Affiliate</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the affiliated order search model
    /// </returns>
    protected virtual async Task<AffiliatedOrderSearchModel> PrepareAffiliatedOrderSearchModelAsync(AffiliatedOrderSearchModel searchModel, Affiliate affiliate)
    {
        ArgumentNullException.ThrowIfNull(searchModel);

        ArgumentNullException.ThrowIfNull(affiliate);

        searchModel.AffliateId = affiliate.Id;

        //prepare available order, payment and shipping statuses
        await _baseAdminModelFactory.PrepareOrderStatusesAsync(searchModel.AvailableOrderStatuses);
        await _baseAdminModelFactory.PreparePaymentStatusesAsync(searchModel.AvailablePaymentStatuses);
        await _baseAdminModelFactory.PrepareShippingStatusesAsync(searchModel.AvailableShippingStatuses);

        //prepare page parameters
        searchModel.SetGridPageSize();

        return searchModel;
    }

    /// <summary>
    /// Prepare affiliated customer search model
    /// </summary>
    /// <param name="searchModel">Affiliated customer search model</param>
    /// <param name="affiliate">Affiliate</param>
    /// <returns>Affiliated customer search model</returns>
    protected virtual AffiliatedCustomerSearchModel PrepareAffiliatedCustomerSearchModel(AffiliatedCustomerSearchModel searchModel, Affiliate affiliate)
    {
        ArgumentNullException.ThrowIfNull(searchModel);

        ArgumentNullException.ThrowIfNull(affiliate);

        searchModel.AffliateId = affiliate.Id;

        //prepare page parameters
        searchModel.SetGridPageSize();

        return searchModel;
    }

    #endregion

    #region Methods

    /// <summary>
    /// Prepare affiliate search model
    /// </summary>
    /// <param name="searchModel">Affiliate search model</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the affiliate search model
    /// </returns>
    public virtual Task<AffiliateSearchModel> PrepareAffiliateSearchModelAsync(AffiliateSearchModel searchModel)
    {
        ArgumentNullException.ThrowIfNull(searchModel);

        //prepare page parameters
        searchModel.SetGridPageSize();

        return Task.FromResult(searchModel);
    }

    /// <summary>
    /// Prepare paged affiliate list model
    /// </summary>
    /// <param name="searchModel">Affiliate search model</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the affiliate list model
    /// </returns>
    public virtual async Task<AffiliateListModel> PrepareAffiliateListModelAsync(AffiliateSearchModel searchModel)
    {
        ArgumentNullException.ThrowIfNull(searchModel);

        //get affiliates
        var affiliates = await _affiliateService.GetAllAffiliatesAsync(searchModel.SearchFriendlyUrlName,
            searchModel.SearchFirstName,
            searchModel.SearchLastName,
            searchModel.LoadOnlyWithOrders,
            searchModel.OrdersCreatedFromUtc,
            searchModel.OrdersCreatedToUtc,
            searchModel.Page - 1, searchModel.PageSize, true);

        //prepare list model
        var model = await new AffiliateListModel().PrepareToGridAsync(searchModel, affiliates, () =>
        {
            //fill in model values from the entity
            return affiliates.SelectAwait(async affiliate =>
            {
                var address = await _addressService.GetAddressByIdAsync(affiliate.AddressId);

                var affiliateModel = affiliate.ToModel<AffiliateModel>();
                affiliateModel.Address = address.ToModel<AddressModel>();
                affiliateModel.Address.CountryName = (await _countryService.GetCountryByAddressAsync(address))?.Name;
                affiliateModel.Address.StateProvinceName = (await _stateProvinceService.GetStateProvinceByAddressAsync(address))?.Name;

                return affiliateModel;
            });
        });

        return model;
    }

    /// <summary>
    /// Prepare affiliate model
    /// </summary>
    /// <param name="model">Affiliate model</param>
    /// <param name="affiliate">Affiliate</param>
    /// <param name="excludeProperties">Whether to exclude populating of some properties of model</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the affiliate model
    /// </returns>
    public virtual async Task<AffiliateModel> PrepareAffiliateModelAsync(AffiliateModel model, Affiliate affiliate, bool excludeProperties = false)
    {
        //fill in model values from the entity
        if (affiliate != null)
        {
            model ??= affiliate.ToModel<AffiliateModel>();
            model.Url = await _affiliateService.GenerateUrlAsync(affiliate);

            //prepare nested search models
            await PrepareAffiliatedOrderSearchModelAsync(model.AffiliatedOrderSearchModel, affiliate);
            PrepareAffiliatedCustomerSearchModel(model.AffiliatedCustomerSearchModel, affiliate);

            //whether to fill in some of properties
            if (!excludeProperties)
            {
                model.AdminComment = affiliate.AdminComment;
                model.FriendlyUrlName = affiliate.FriendlyUrlName;
                model.Active = affiliate.Active;
            }
        }

        //prepare address model
        var address = await _addressService.GetAddressByIdAsync(affiliate?.AddressId ?? 0);
        if (!excludeProperties && address != null)
            model.Address = address.ToModel(model.Address);
        await _addressModelFactory.PrepareAddressModelAsync(model.Address, address);
        model.Address.FirstNameRequired = true;
        model.Address.LastNameRequired = true;
        model.Address.EmailRequired = true;
        model.Address.CountryRequired = true;
        model.Address.CountyRequired = true;
        model.Address.CityRequired = true;
        model.Address.StreetAddressRequired = true;
        model.Address.ZipPostalCodeRequired = true;
        model.Address.PhoneRequired = true;

        return model;
    }

    /// <summary>
    /// Prepare paged affiliated order list model
    /// </summary>
    /// <param name="searchModel">Affiliated order search model</param>
    /// <param name="affiliate">Affiliate</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the affiliated order list model
    /// </returns>
    public virtual async Task<AffiliatedOrderListModel> PrepareAffiliatedOrderListModelAsync(AffiliatedOrderSearchModel searchModel, Affiliate affiliate)
    {
        ArgumentNullException.ThrowIfNull(searchModel);

        ArgumentNullException.ThrowIfNull(affiliate);

        //get parameters to filter orders
        var startDateValue = !searchModel.StartDate.HasValue ? null
            : (DateTime?)_dateTimeHelper.ConvertToUtcTime(searchModel.StartDate.Value, await _dateTimeHelper.GetCurrentTimeZoneAsync());
        var endDateValue = !searchModel.EndDate.HasValue ? null
            : (DateTime?)_dateTimeHelper.ConvertToUtcTime(searchModel.EndDate.Value, await _dateTimeHelper.GetCurrentTimeZoneAsync()).AddDays(1);
        var orderStatusIds = searchModel.OrderStatusId > 0 ? new List<int> { searchModel.OrderStatusId } : null;
        var paymentStatusIds = searchModel.PaymentStatusId > 0 ? new List<int> { searchModel.PaymentStatusId } : null;
        var shippingStatusIds = searchModel.ShippingStatusId > 0 ? new List<int> { searchModel.ShippingStatusId } : null;

        //get orders
        var orders = await _orderService.SearchOrdersAsync(createdFromUtc: startDateValue,
            createdToUtc: endDateValue,
            osIds: orderStatusIds,
            psIds: paymentStatusIds,
            ssIds: shippingStatusIds,
            affiliateId: affiliate.Id,
            pageIndex: searchModel.Page - 1, pageSize: searchModel.PageSize);

        //prepare list model
        var model = await new AffiliatedOrderListModel().PrepareToGridAsync(searchModel, orders, () =>
        {
            //fill in model values from the entity
            return orders.SelectAwait(async order =>
            {
                var affiliatedOrderModel = order.ToModel<AffiliatedOrderModel>();

                //fill in additional values (not existing in the entity)
                affiliatedOrderModel.OrderStatus = await _localizationService.GetLocalizedEnumAsync(order.OrderStatus);
                affiliatedOrderModel.PaymentStatus = await _localizationService.GetLocalizedEnumAsync(order.PaymentStatus);
                affiliatedOrderModel.ShippingStatus = await _localizationService.GetLocalizedEnumAsync(order.ShippingStatus);
                affiliatedOrderModel.OrderTotal = await _priceFormatter.FormatPriceAsync(order.OrderTotal, true, false);

                affiliatedOrderModel.CreatedOn = await _dateTimeHelper.ConvertToUserTimeAsync(order.CreatedOnUtc, DateTimeKind.Utc);

                return affiliatedOrderModel;
            });
        });

        return model;
    }

    /// <summary>
    /// Prepare paged affiliated customer list model
    /// </summary>
    /// <param name="searchModel">Affiliated customer search model</param>
    /// <param name="affiliate">Affiliate</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the affiliated customer list model
    /// </returns>
    public virtual async Task<AffiliatedCustomerListModel> PrepareAffiliatedCustomerListModelAsync(AffiliatedCustomerSearchModel searchModel,
        Affiliate affiliate)
    {
        ArgumentNullException.ThrowIfNull(searchModel);

        ArgumentNullException.ThrowIfNull(affiliate);

        //get customers
        var customers = await _customerService.GetAllCustomersAsync(affiliateId: affiliate.Id,
            pageIndex: searchModel.Page - 1, pageSize: searchModel.PageSize);

        //prepare list model
        var model = new AffiliatedCustomerListModel().PrepareToGrid(searchModel, customers, () =>
        {
            //fill in model values from the entity
            return customers.Select(customer =>
            {
                var affiliatedCustomerModel = customer.ToModel<AffiliatedCustomerModel>();
                affiliatedCustomerModel.Name = customer.Email;

                return affiliatedCustomerModel;
            });
        });

        return model;
    }

    #endregion
}