using Grand.Business.Core.Extensions;
using Grand.Business.Core.Interfaces.Catalog.Categories;
using Grand.Business.Core.Interfaces.Catalog.Discounts;
using Grand.Business.Core.Interfaces.Catalog.Products;
using Grand.Business.Core.Interfaces.Common.Localization;
using Grand.Business.Core.Interfaces.Common.Seo;
using Grand.Business.Core.Interfaces.Common.Stores;
using Grand.Business.Core.Interfaces.Customers;
using Grand.Business.Core.Interfaces.Storage;
using Grand.Domain.Catalog;
using Grand.Domain.Discounts;
using Grand.Domain.Seo;
using Grand.Web.Admin.Extensions;
using Grand.Web.Admin.Extensions.Mapping;
using Grand.Web.Admin.Interfaces;
using Grand.Web.Admin.Models.Catalog;
using Grand.Web.Common.Extensions;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace Grand.Web.Admin.Services;

public class CategoryViewModelService : ICategoryViewModelService
{
    private readonly CatalogSettings _catalogSettings;
    private readonly ICategoryLayoutService _categoryLayoutService;
    private readonly ICategoryService _categoryService;
    private readonly IDiscountService _discountService;
    private readonly ILanguageService _languageService;
    private readonly IPictureService _pictureService;
    private readonly IProductCategoryService _productCategoryService;
    private readonly IProductService _productService;
    private readonly SeoSettings _seoSettings;
    private readonly ISlugService _slugService;
    private readonly IStoreService _storeService;
    private readonly ITranslationService _translationService;
    private readonly IVendorService _vendorService;

    public CategoryViewModelService(
        ICategoryService categoryService,
        IProductCategoryService productCategoryService,
        ICategoryLayoutService categoryLayoutService,
        IDiscountService discountService,
        ITranslationService translationService,
        IStoreService storeService,
        IPictureService pictureService,
        ISlugService slugService,
        IProductService productService,
        IVendorService vendorService,
        ILanguageService languageService,
        CatalogSettings catalogSettings,
        SeoSettings seoSettings)
    {
        _categoryService = categoryService;
        _productCategoryService = productCategoryService;
        _categoryLayoutService = categoryLayoutService;
        _discountService = discountService;
        _translationService = translationService;
        _storeService = storeService;
        _slugService = slugService;
        _productService = productService;
        _pictureService = pictureService;
        _vendorService = vendorService;
        _languageService = languageService;
        _catalogSettings = catalogSettings;
        _seoSettings = seoSettings;
    }

    public virtual async Task<CategoryListModel> PrepareCategoryListModel(string storeId)
    {
        var model = new CategoryListModel();
        model.AvailableStores.Add(new SelectListItem
            { Text = _translationService.GetResource("Admin.Common.All"), Value = "" });
        foreach (var s in (await _storeService.GetAllStores()).Where(x =>
                     x.Id == storeId || string.IsNullOrWhiteSpace(storeId)))
            model.AvailableStores.Add(new SelectListItem { Text = s.Shortcut, Value = s.Id });
        return model;
    }

    public virtual async Task<(IEnumerable<CategoryModel> categoryListModel, int totalCount)> PrepareCategoryListModel(
        CategoryListModel model, int pageIndex, int pageSize)
    {
        var categories = await _categoryService.GetAllCategories(
            categoryName: model.SearchCategoryName,
            storeId: model.SearchStoreId,
            pageSize: pageSize,
            pageIndex: pageIndex - 1,
            showHidden: true);

        var categoryListModel = new List<CategoryModel>();
        foreach (var x in categories)
        {
            var categoryModel = x.ToModel();
            categoryModel.Breadcrumb = await _categoryService.GetFormattedBreadCrumb(x);
            categoryListModel.Add(categoryModel);
        }

        return (categoryListModel, categories.TotalCount);
    }

    public virtual async Task<CategoryModel> PrepareCategoryModel(string storeId)
    {
        var model = new CategoryModel();
        //sort options
        PrepareSortOptionsModel(model);
        //layouts
        await PrepareLayoutsModel(model);
        //discounts
        await PrepareDiscountModel(model, null, true, storeId);

        //default values
        model.PageSize = _catalogSettings.DefaultCategoryPageSize;
        model.PageSizeOptions = _catalogSettings.DefaultCategoryPageSizeOptions;
        model.Published = true;
        model.IncludeInMenu = true;
        model.AllowCustomersToSelectPageSize = true;
        return model;
    }

    public virtual async Task<CategoryModel> PrepareCategoryModel(CategoryModel model, Category category,
        string storeId)
    {
        //sort options
        PrepareSortOptionsModel(model);
        //layouts
        await PrepareLayoutsModel(model);
        //discounts
        await PrepareDiscountModel(model, category, false, storeId);
        return model;
    }

    public async Task<Category> InsertCategoryModel(CategoryModel model)
    {
        var category = model.ToEntity();
        var allDiscounts = await _discountService.GetDiscountsQuery(DiscountType.AssignedToCategories);
        foreach (var discount in allDiscounts)
            if (model.SelectedDiscountIds != null && model.SelectedDiscountIds.Contains(discount.Id))
                category.AppliedDiscounts.Add(discount.Id);
        await _categoryService.InsertCategory(category);

        //locales
        category.Locales =
            await model.Locales.ToTranslationProperty(category, x => x.Name, _seoSettings, _slugService,
                _languageService);
        model.SeName = await category.ValidateSeName(model.SeName, category.Name, true, _seoSettings, _slugService,
            _languageService);
        category.SeName = model.SeName;
        await _categoryService.UpdateCategory(category);

        await _slugService.SaveSlug(category, model.SeName, "");

        //update picture seo file name
        await _pictureService.UpdatePictureSeoNames(category.PictureId, category.Name);

        return category;
    }

    public virtual async Task<Category> UpdateCategoryModel(Category category, CategoryModel model)
    {
        var prevPictureId = category.PictureId;
        category = model.ToEntity(category);
        model.SeName = await category.ValidateSeName(model.SeName, category.Name, true, _seoSettings, _slugService,
            _languageService);
        category.SeName = model.SeName;
        //locales
        category.Locales =
            await model.Locales.ToTranslationProperty(category, x => x.Name, _seoSettings, _slugService,
                _languageService);
        //discounts
        var allDiscounts = await _discountService.GetDiscountsQuery(DiscountType.AssignedToCategories);
        foreach (var discount in allDiscounts)
            if (model.SelectedDiscountIds != null && model.SelectedDiscountIds.Contains(discount.Id))
            {
                //new discount
                if (category.AppliedDiscounts.Count(d => d == discount.Id) == 0)
                    category.AppliedDiscounts.Add(discount.Id);
            }
            else
            {
                //remove discount
                if (category.AppliedDiscounts.Count(d => d == discount.Id) > 0)
                    category.AppliedDiscounts.Remove(discount.Id);
            }

        await _categoryService.UpdateCategory(category);

        //search engine name
        await _slugService.SaveSlug(category, model.SeName, "");

        //delete an old picture (if deleted or updated)
        if (!string.IsNullOrEmpty(prevPictureId) && prevPictureId != category.PictureId)
        {
            var prevPicture = await _pictureService.GetPictureById(prevPictureId);
            if (prevPicture != null)
                await _pictureService.DeletePicture(prevPicture);
        }

        //update picture seo file name
        await _pictureService.UpdatePictureSeoNames(category.PictureId, category.Name);

        return category;
    }

    public virtual async Task DeleteCategory(Category category)
    {
        await _categoryService.DeleteCategory(category);
    }

    public virtual async Task<(IEnumerable<CategoryModel.CategoryProductModel> categoryProductModels, int totalCount)>
        PrepareCategoryProductModel(string categoryId, int pageIndex, int pageSize)
    {
        var productCategories = await _productCategoryService.GetProductCategoriesByCategoryId(categoryId,
            pageIndex - 1, pageSize, true);

        var categoryproducts = new List<CategoryModel.CategoryProductModel>();
        foreach (var item in productCategories)
        {
            var pc = new CategoryModel.CategoryProductModel {
                Id = item.Id,
                CategoryId = item.CategoryId,
                ProductId = item.ProductId,
                ProductName = (await _productService.GetProductById(item.ProductId))?.Name,
                IsFeaturedProduct = item.IsFeaturedProduct,
                DisplayOrder = item.DisplayOrder
            };
            categoryproducts.Add(pc);
        }

        return (categoryproducts, productCategories.TotalCount);
    }

    public virtual async Task<ProductCategory> UpdateProductCategoryModel(CategoryModel.CategoryProductModel model)
    {
        var product = await _productService.GetProductById(model.ProductId);
        var productCategory = product.ProductCategories.FirstOrDefault(x => x.Id == model.Id);
        if (productCategory == null)
            throw new ArgumentException("No product category mapping found with the specified id");

        productCategory.IsFeaturedProduct = model.IsFeaturedProduct;
        productCategory.DisplayOrder = model.DisplayOrder;
        await _productCategoryService.UpdateProductCategory(productCategory, product.Id);
        return productCategory;
    }

    public virtual async Task DeleteProductCategoryModel(string id, string productId)
    {
        var product = await _productService.GetProductById(productId);
        if (product == null)
            throw new ArgumentException("No product found with the specified id");

        var productCategory = product.ProductCategories.FirstOrDefault(x => x.Id == id);
        if (productCategory == null)
            throw new ArgumentException("No product category mapping found with the specified id");
        await _productCategoryService.DeleteProductCategory(productCategory, product.Id);
    }

    public virtual async Task<CategoryModel.AddCategoryProductModel> PrepareAddCategoryProductModel(string storeId)
    {
        var model = new CategoryModel.AddCategoryProductModel();
        //stores
        model.AvailableStores.Add(new SelectListItem
            { Text = _translationService.GetResource("Admin.Common.All"), Value = " " });
        foreach (var s in (await _storeService.GetAllStores()).Where(x =>
                     x.Id == storeId || string.IsNullOrWhiteSpace(storeId)))
            model.AvailableStores.Add(new SelectListItem { Text = s.Shortcut, Value = s.Id });

        //vendors
        model.AvailableVendors.Add(new SelectListItem
            { Text = _translationService.GetResource("Admin.Common.All"), Value = " " });
        foreach (var v in await _vendorService.GetAllVendors(showHidden: true))
            model.AvailableVendors.Add(new SelectListItem { Text = v.Name, Value = v.Id });

        //product types
        model.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList().ToList();
        model.AvailableProductTypes.Insert(0,
            new SelectListItem { Text = _translationService.GetResource("Admin.Common.All"), Value = "0" });
        return model;
    }

    public virtual async Task InsertCategoryProductModel(CategoryModel.AddCategoryProductModel model)
    {
        foreach (var id in model.SelectedProductIds)
        {
            var product = await _productService.GetProductById(id);
            if (product != null)
                if (!product.ProductCategories.Any(x => x.CategoryId == model.CategoryId))
                    await _productCategoryService.InsertProductCategory(
                        new ProductCategory {
                            CategoryId = model.CategoryId,
                            IsFeaturedProduct = false,
                            DisplayOrder = 1
                        }, product.Id);
        }
    }

    public virtual async Task<(IList<ProductModel> products, int totalCount)> PrepareProductModel(
        CategoryModel.AddCategoryProductModel model, int pageIndex, int pageSize)
    {
        var products = await _productService.PrepareProductList(model.SearchCategoryId, model.SearchBrandId,
            model.SearchCollectionId, model.SearchStoreId, model.SearchVendorId, model.SearchProductTypeId,
            model.SearchProductName, pageIndex, pageSize);
        return (products.Select(x => x.ToModel()).ToList(), products.TotalCount);
    }

    protected virtual async Task PrepareLayoutsModel(CategoryModel model)
    {
        ArgumentNullException.ThrowIfNull(model);

        var layouts = await _categoryLayoutService.GetAllCategoryLayouts();
        foreach (var layout in layouts)
            model.AvailableCategoryLayouts.Add(new SelectListItem {
                Text = layout.Name,
                Value = layout.Id
            });
    }

    protected virtual async Task PrepareDiscountModel(CategoryModel model, Category category, bool excludeProperties,
        string storeId)
    {
        ArgumentNullException.ThrowIfNull(model);

        model.AvailableDiscounts = (await _discountService
                .GetDiscountsQuery(DiscountType.AssignedToCategories, storeId))
            .Select(d => d.ToModel())
            .ToList();

        if (!excludeProperties && category != null) model.SelectedDiscountIds = category.AppliedDiscounts.ToArray();
    }

    protected virtual void PrepareSortOptionsModel(CategoryModel model)
    {
        ArgumentNullException.ThrowIfNull(model);

        model.AvailableSortOptions = ProductSortingEnum.Position.ToSelectList().ToList();
        model.AvailableSortOptions.Insert(0, new SelectListItem { Text = "None", Value = "-1" });
    }
}