using Microsoft.AspNetCore.Mvc;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Directory;
using Nop.Core.Domain.Orders;
using Nop.Services.Attributes;
using Nop.Services.Directory;
using Nop.Services.Localization;
using Nop.Services.Logging;
using Nop.Services.Messages;
using Nop.Services.Security;
using Nop.Services.Stores;
using Nop.Web.Areas.Admin.Factories;
using Nop.Web.Areas.Admin.Infrastructure.Mapper.Extensions;
using Nop.Web.Areas.Admin.Models.Orders;
using Nop.Web.Framework.Mvc;
using Nop.Web.Framework.Mvc.Filters;

namespace Nop.Web.Areas.Admin.Controllers;

public partial class CheckoutAttributeController : BaseAdminController
{
    #region Fields

    protected readonly CurrencySettings _currencySettings;
    protected readonly IAttributeParser<CheckoutAttribute, CheckoutAttributeValue> _checkoutAttributeParser;
    protected readonly IAttributeService<CheckoutAttribute, CheckoutAttributeValue> _checkoutAttributeService;
    protected readonly ICheckoutAttributeModelFactory _checkoutAttributeModelFactory;
    protected readonly ICurrencyService _currencyService;
    protected readonly ICustomerActivityService _customerActivityService;
    protected readonly ILocalizationService _localizationService;
    protected readonly ILocalizedEntityService _localizedEntityService;
    protected readonly INotificationService _notificationService;
    protected readonly IMeasureService _measureService;
    protected readonly IPermissionService _permissionService;
    protected readonly IStoreMappingService _storeMappingService;
    protected readonly IStoreService _storeService;
    protected readonly MeasureSettings _measureSettings;

    #endregion

    #region Ctor

    public CheckoutAttributeController(CurrencySettings currencySettings,
        IAttributeParser<CheckoutAttribute, CheckoutAttributeValue> checkoutAttributeParser,
        IAttributeService<CheckoutAttribute, CheckoutAttributeValue> checkoutAttributeService,
        ICheckoutAttributeModelFactory checkoutAttributeModelFactory,
        ICurrencyService currencyService,
        ICustomerActivityService customerActivityService,
        ILocalizationService localizationService,
        ILocalizedEntityService localizedEntityService,
        INotificationService notificationService,
        IMeasureService measureService,
        IPermissionService permissionService,
        IStoreMappingService storeMappingService,
        IStoreService storeService,
        MeasureSettings measureSettings)
    {
        _currencySettings = currencySettings;
        _checkoutAttributeParser = checkoutAttributeParser;
        _checkoutAttributeService = checkoutAttributeService;
        _checkoutAttributeModelFactory = checkoutAttributeModelFactory;
        _currencyService = currencyService;
        _customerActivityService = customerActivityService;
        _localizationService = localizationService;
        _localizedEntityService = localizedEntityService;
        _notificationService = notificationService;
        _measureService = measureService;
        _permissionService = permissionService;
        _storeMappingService = storeMappingService;
        _storeService = storeService;
        _measureSettings = measureSettings;
    }

    #endregion

    #region Utilities

    protected virtual async Task UpdateAttributeLocalesAsync(CheckoutAttribute checkoutAttribute, CheckoutAttributeModel model)
    {
        foreach (var localized in model.Locales)
        {
            await _localizedEntityService.SaveLocalizedValueAsync(checkoutAttribute,
                x => x.Name,
                localized.Name,
                localized.LanguageId);

            await _localizedEntityService.SaveLocalizedValueAsync(checkoutAttribute,
                x => x.TextPrompt,
                localized.TextPrompt,
                localized.LanguageId);

            await _localizedEntityService.SaveLocalizedValueAsync(checkoutAttribute,
                x => x.DefaultValue,
                localized.DefaultValue,
                localized.LanguageId);
        }
    }

    protected virtual async Task UpdateValueLocalesAsync(CheckoutAttributeValue checkoutAttributeValue, CheckoutAttributeValueModel model)
    {
        foreach (var localized in model.Locales)
        {
            await _localizedEntityService.SaveLocalizedValueAsync(checkoutAttributeValue,
                x => x.Name,
                localized.Name,
                localized.LanguageId);
        }
    }

    protected virtual async Task SaveStoreMappingsAsync(CheckoutAttribute checkoutAttribute, CheckoutAttributeModel model)
    {
        checkoutAttribute.LimitedToStores = model.SelectedStoreIds.Any();
        await _checkoutAttributeService.UpdateAttributeAsync(checkoutAttribute);

        var existingStoreMappings = await _storeMappingService.GetStoreMappingsAsync(checkoutAttribute);
        var allStores = await _storeService.GetAllStoresAsync();
        foreach (var store in allStores)
        {
            if (model.SelectedStoreIds.Contains(store.Id))
            {
                //new store
                if (!existingStoreMappings.Any(sm => sm.StoreId == store.Id))
                    await _storeMappingService.InsertStoreMappingAsync(checkoutAttribute, store.Id);
            }
            else
            {
                //remove store
                var storeMappingToDelete = existingStoreMappings.FirstOrDefault(sm => sm.StoreId == store.Id);
                if (storeMappingToDelete != null)
                    await _storeMappingService.DeleteStoreMappingAsync(storeMappingToDelete);
            }
        }
    }

    protected virtual async Task SaveConditionAttributesAsync(CheckoutAttribute checkoutAttribute, CheckoutAttributeModel model)
    {
        string attributesXml = null;

        if (model.ConditionModel.EnableCondition)
        {
            var attribute = await _checkoutAttributeService.GetAttributeByIdAsync(model.ConditionModel.SelectedAttributeId);
            if (attribute != null)
            {
                switch (attribute.AttributeControlType)
                {
                    case AttributeControlType.DropdownList:
                    case AttributeControlType.RadioList:
                    case AttributeControlType.ColorSquares:
                    case AttributeControlType.ImageSquares:
                    {
                        var selectedAttribute = model.ConditionModel.ConditionAttributes
                            .FirstOrDefault(x => x.Id == model.ConditionModel.SelectedAttributeId);
                        var selectedValue = selectedAttribute?.SelectedValueId;

                        //for conditions we should empty values save even when nothing is selected
                        //otherwise "attributesXml" will be empty
                        //hence we won't be able to find a selected attribute
                        attributesXml = _checkoutAttributeParser.AddAttribute(null, attribute, string.IsNullOrEmpty(selectedValue) ? string.Empty : selectedValue);
                    }
                    break;

                    case AttributeControlType.Checkboxes:
                    {
                        var selectedAttribute = model.ConditionModel.ConditionAttributes
                            .FirstOrDefault(x => x.Id == model.ConditionModel.SelectedAttributeId);
                        var selectedValues = selectedAttribute?.Values
                            .Where(x => x.Selected)
                            .Select(x => x.Value)
                            .ToList();

                        if (selectedValues?.Any() ?? false)
                            foreach (var value in selectedValues)
                                attributesXml = _checkoutAttributeParser.AddAttribute(attributesXml, attribute, value);
                        else
                            attributesXml = _checkoutAttributeParser.AddAttribute(null, attribute, string.Empty);
                    }
                    break;

                    case AttributeControlType.ReadonlyCheckboxes:
                    case AttributeControlType.TextBox:
                    case AttributeControlType.MultilineTextbox:
                    case AttributeControlType.Datepicker:
                    case AttributeControlType.FileUpload:
                    default:
                        //these attribute types are not supported as conditions
                        break;
                }
            }
        }

        checkoutAttribute.ConditionAttributeXml = attributesXml;
    }

    #endregion

    #region Checkout attributes

    public virtual IActionResult Index()
    {
        return RedirectToAction("List");
    }

    [CheckPermission(StandardPermission.Catalog.CHECKOUT_ATTRIBUTES_VIEW)]
    public virtual async Task<IActionResult> List()
    {
        //prepare model
        var model = await _checkoutAttributeModelFactory.PrepareCheckoutAttributeSearchModelAsync(new CheckoutAttributeSearchModel());

        return View(model);
    }

    [HttpPost]
    [CheckPermission(StandardPermission.Catalog.CHECKOUT_ATTRIBUTES_VIEW)]
    public virtual async Task<IActionResult> List(CheckoutAttributeSearchModel searchModel)
    {
        //prepare model
        var model = await _checkoutAttributeModelFactory.PrepareCheckoutAttributeListModelAsync(searchModel);

        return Json(model);
    }

    [CheckPermission(StandardPermission.Catalog.CHECKOUT_ATTRIBUTES_CREATE_EDIT_DELETE)]
    public virtual async Task<IActionResult> Create()
    {
        //prepare model
        var model = await _checkoutAttributeModelFactory.PrepareCheckoutAttributeModelAsync(new CheckoutAttributeModel(), null);

        return View(model);
    }

    [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")]
    [CheckPermission(StandardPermission.Catalog.CHECKOUT_ATTRIBUTES_CREATE_EDIT_DELETE)]
    public virtual async Task<IActionResult> Create(CheckoutAttributeModel model, bool continueEditing)
    {
        if (ModelState.IsValid)
        {
            var checkoutAttribute = model.ToEntity<CheckoutAttribute>();
            await _checkoutAttributeService.InsertAttributeAsync(checkoutAttribute);

            //locales
            await UpdateAttributeLocalesAsync(checkoutAttribute, model);

            //stores
            await SaveStoreMappingsAsync(checkoutAttribute, model);

            //activity log
            await _customerActivityService.InsertActivityAsync("AddNewCheckoutAttribute",
                string.Format(await _localizationService.GetResourceAsync("ActivityLog.AddNewCheckoutAttribute"), checkoutAttribute.Name), checkoutAttribute);

            _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Catalog.Attributes.CheckoutAttributes.Added"));

            if (!continueEditing)
                return RedirectToAction("List");

            return RedirectToAction("Edit", new { id = checkoutAttribute.Id });
        }

        //prepare model
        model = await _checkoutAttributeModelFactory.PrepareCheckoutAttributeModelAsync(model, null, true);

        //if we got this far, something failed, redisplay form
        return View(model);
    }

    [CheckPermission(StandardPermission.Catalog.CHECKOUT_ATTRIBUTES_VIEW)]
    public virtual async Task<IActionResult> Edit(int id)
    {
        //try to get a checkout attribute with the specified id
        var checkoutAttribute = await _checkoutAttributeService.GetAttributeByIdAsync(id);
        if (checkoutAttribute == null)
            return RedirectToAction("List");

        //prepare model
        var model = await _checkoutAttributeModelFactory.PrepareCheckoutAttributeModelAsync(null, checkoutAttribute);

        return View(model);
    }

    [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")]
    [CheckPermission(StandardPermission.Catalog.CHECKOUT_ATTRIBUTES_CREATE_EDIT_DELETE)]
    public virtual async Task<IActionResult> Edit(CheckoutAttributeModel model, bool continueEditing)
    {
        //try to get a checkout attribute with the specified id
        var checkoutAttribute = await _checkoutAttributeService.GetAttributeByIdAsync(model.Id);
        if (checkoutAttribute == null)
            return RedirectToAction("List");

        if (ModelState.IsValid)
        {
            checkoutAttribute = model.ToEntity(checkoutAttribute);
            await SaveConditionAttributesAsync(checkoutAttribute, model);
            await _checkoutAttributeService.UpdateAttributeAsync(checkoutAttribute);

            //locales
            await UpdateAttributeLocalesAsync(checkoutAttribute, model);

            //stores
            await SaveStoreMappingsAsync(checkoutAttribute, model);

            //activity log
            await _customerActivityService.InsertActivityAsync("EditCheckoutAttribute",
                string.Format(await _localizationService.GetResourceAsync("ActivityLog.EditCheckoutAttribute"), checkoutAttribute.Name), checkoutAttribute);

            _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Catalog.Attributes.CheckoutAttributes.Updated"));

            if (!continueEditing)
                return RedirectToAction("List");

            return RedirectToAction("Edit", new { id = checkoutAttribute.Id });
        }

        //prepare model
        model = await _checkoutAttributeModelFactory.PrepareCheckoutAttributeModelAsync(model, checkoutAttribute, true);

        //if we got this far, something failed, redisplay form
        return View(model);
    }

    [HttpPost]
    [CheckPermission(StandardPermission.Catalog.CHECKOUT_ATTRIBUTES_CREATE_EDIT_DELETE)]
    public virtual async Task<IActionResult> Delete(int id)
    {
        //try to get a checkout attribute with the specified id
        var checkoutAttribute = await _checkoutAttributeService.GetAttributeByIdAsync(id);
        if (checkoutAttribute == null)
            return RedirectToAction("List");

        await _checkoutAttributeService.DeleteAttributeAsync(checkoutAttribute);

        //activity log
        await _customerActivityService.InsertActivityAsync("DeleteCheckoutAttribute",
            string.Format(await _localizationService.GetResourceAsync("ActivityLog.DeleteCheckoutAttribute"), checkoutAttribute.Name), checkoutAttribute);

        _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Catalog.Attributes.CheckoutAttributes.Deleted"));

        return RedirectToAction("List");
    }

    [HttpPost]
    [CheckPermission(StandardPermission.Catalog.CHECKOUT_ATTRIBUTES_CREATE_EDIT_DELETE)]
    public virtual async Task<IActionResult> DeleteSelected(ICollection<int> selectedIds)
    {
        if (selectedIds == null || !selectedIds.Any())
            return NoContent();

        var checkoutAttributes = await _checkoutAttributeService.GetAttributeByIdsAsync(selectedIds.ToArray());
        await _checkoutAttributeService.DeleteAttributesAsync(checkoutAttributes);

        foreach (var checkoutAttribute in checkoutAttributes)
        {
            //activity log
            await _customerActivityService.InsertActivityAsync("DeleteCheckoutAttribute",
                string.Format(await _localizationService.GetResourceAsync("ActivityLog.DeleteCheckoutAttribute"), checkoutAttribute.Name), checkoutAttribute);
        }

        return Json(new { Result = true });
    }

    #endregion

    #region Checkout attribute values

    [HttpPost]
    [CheckPermission(StandardPermission.Catalog.CHECKOUT_ATTRIBUTES_VIEW)]
    public virtual async Task<IActionResult> ValueList(CheckoutAttributeValueSearchModel searchModel)
    {
        //try to get a checkout attribute with the specified id
        var checkoutAttribute = await _checkoutAttributeService.GetAttributeByIdAsync(searchModel.CheckoutAttributeId)
            ?? throw new ArgumentException("No checkout attribute found with the specified id");

        //prepare model
        var model = await _checkoutAttributeModelFactory.PrepareCheckoutAttributeValueListModelAsync(searchModel, checkoutAttribute);

        return Json(model);
    }

    [CheckPermission(StandardPermission.Catalog.CHECKOUT_ATTRIBUTES_CREATE_EDIT_DELETE)]
    public virtual async Task<IActionResult> ValueCreatePopup(int checkoutAttributeId)
    {
        //try to get a checkout attribute with the specified id
        var checkoutAttribute = await _checkoutAttributeService.GetAttributeByIdAsync(checkoutAttributeId);
        if (checkoutAttribute == null)
            return RedirectToAction("List");

        //prepare model
        var model = await _checkoutAttributeModelFactory
            .PrepareCheckoutAttributeValueModelAsync(new CheckoutAttributeValueModel(), checkoutAttribute, null);

        return View(model);
    }

    [HttpPost]
    [CheckPermission(StandardPermission.Catalog.CHECKOUT_ATTRIBUTES_CREATE_EDIT_DELETE)]
    public virtual async Task<IActionResult> ValueCreatePopup(CheckoutAttributeValueModel model)
    {
        //try to get a checkout attribute with the specified id
        var checkoutAttribute = await _checkoutAttributeService.GetAttributeByIdAsync(model.AttributeId);
        if (checkoutAttribute == null)
            return RedirectToAction("List");

        model.PrimaryStoreCurrencyCode = (await _currencyService.GetCurrencyByIdAsync(_currencySettings.PrimaryStoreCurrencyId)).CurrencyCode;
        model.BaseWeightIn = (await _measureService.GetMeasureWeightByIdAsync(_measureSettings.BaseWeightId)).Name;

        if (checkoutAttribute.AttributeControlType == AttributeControlType.ColorSquares)
        {
            //ensure valid color is chosen/entered
            if (string.IsNullOrEmpty(model.ColorSquaresRgb))
                ModelState.AddModelError(string.Empty, "Color is required");

            try
            {
                //ensure color is valid (can be instantiated)
                System.Drawing.ColorTranslator.FromHtml(model.ColorSquaresRgb);
            }
            catch (Exception exc)
            {
                ModelState.AddModelError(string.Empty, exc.Message);
            }
        }

        if (ModelState.IsValid)
        {
            var checkoutAttributeValue = model.ToEntity<CheckoutAttributeValue>();
            await _checkoutAttributeService.InsertAttributeValueAsync(checkoutAttributeValue);

            await UpdateValueLocalesAsync(checkoutAttributeValue, model);

            ViewBag.RefreshPage = true;

            return View(model);
        }

        //prepare model
        model = await _checkoutAttributeModelFactory.PrepareCheckoutAttributeValueModelAsync(model, checkoutAttribute, null, true);

        //if we got this far, something failed, redisplay form
        return View(model);
    }

    [CheckPermission(StandardPermission.Catalog.CHECKOUT_ATTRIBUTES_VIEW)]
    public virtual async Task<IActionResult> ValueEditPopup(int id)
    {
        //try to get a checkout attribute value with the specified id
        var checkoutAttributeValue = await _checkoutAttributeService.GetAttributeValueByIdAsync(id);
        if (checkoutAttributeValue == null)
            return RedirectToAction("List");

        //try to get a checkout attribute with the specified id
        var checkoutAttribute = await _checkoutAttributeService.GetAttributeByIdAsync(checkoutAttributeValue.AttributeId);
        if (checkoutAttribute == null)
            return RedirectToAction("List");

        //prepare model
        var model = await _checkoutAttributeModelFactory.PrepareCheckoutAttributeValueModelAsync(null, checkoutAttribute, checkoutAttributeValue);

        return View(model);
    }

    [HttpPost]
    [CheckPermission(StandardPermission.Catalog.CHECKOUT_ATTRIBUTES_CREATE_EDIT_DELETE)]
    public virtual async Task<IActionResult> ValueEditPopup(CheckoutAttributeValueModel model)
    {
        //try to get a checkout attribute value with the specified id
        var checkoutAttributeValue = await _checkoutAttributeService.GetAttributeValueByIdAsync(model.Id);
        if (checkoutAttributeValue == null)
            return RedirectToAction("List");

        //try to get a checkout attribute with the specified id
        var checkoutAttribute = await _checkoutAttributeService.GetAttributeByIdAsync(checkoutAttributeValue.AttributeId);
        if (checkoutAttribute == null)
            return RedirectToAction("List");

        model.PrimaryStoreCurrencyCode = (await _currencyService.GetCurrencyByIdAsync(_currencySettings.PrimaryStoreCurrencyId)).CurrencyCode;
        model.BaseWeightIn = (await _measureService.GetMeasureWeightByIdAsync(_measureSettings.BaseWeightId)).Name;

        if (checkoutAttribute.AttributeControlType == AttributeControlType.ColorSquares)
        {
            //ensure valid color is chosen/entered
            if (string.IsNullOrEmpty(model.ColorSquaresRgb))
                ModelState.AddModelError(string.Empty, "Color is required");

            try
            {
                //ensure color is valid (can be instantiated)
                System.Drawing.ColorTranslator.FromHtml(model.ColorSquaresRgb);
            }
            catch (Exception exc)
            {
                ModelState.AddModelError(string.Empty, exc.Message);
            }
        }

        if (ModelState.IsValid)
        {
            checkoutAttributeValue = model.ToEntity(checkoutAttributeValue);
            await _checkoutAttributeService.UpdateAttributeValueAsync(checkoutAttributeValue);

            await UpdateValueLocalesAsync(checkoutAttributeValue, model);

            ViewBag.RefreshPage = true;

            return View(model);
        }

        //prepare model
        model = await _checkoutAttributeModelFactory.PrepareCheckoutAttributeValueModelAsync(model, checkoutAttribute, checkoutAttributeValue, true);

        //if we got this far, something failed, redisplay form
        return View(model);
    }

    [HttpPost]
    [CheckPermission(StandardPermission.Catalog.CHECKOUT_ATTRIBUTES_CREATE_EDIT_DELETE)]
    public virtual async Task<IActionResult> ValueDelete(int id)
    {
        //try to get a checkout attribute value with the specified id
        var checkoutAttributeValue = await _checkoutAttributeService.GetAttributeValueByIdAsync(id)
            ?? throw new ArgumentException("No checkout attribute value found with the specified id", nameof(id));

        await _checkoutAttributeService.DeleteAttributeValueAsync(checkoutAttributeValue);

        return new NullJsonResult();
    }

    #endregion
}