using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Nop.Core.Domain.Localization;
using Nop.Core.Infrastructure;
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.Localization;
using Nop.Web.Framework.Mvc;
using Nop.Web.Framework.Mvc.Filters;
using Nop.Web.Framework.Mvc.ModelBinding;
using Nop.Web.Framework.Validators;

namespace Nop.Web.Areas.Admin.Controllers;

public partial class LanguageController : BaseAdminController
{
    #region Const

    protected const string FLAGS_PATH = @"images\flags";

    #endregion

    #region Fields

    protected readonly ICustomerActivityService _customerActivityService;
    protected readonly ILanguageModelFactory _languageModelFactory;
    protected readonly ILanguageService _languageService;
    protected readonly ILocalizationService _localizationService;
    protected readonly INopFileProvider _fileProvider;
    protected readonly INotificationService _notificationService;
    protected readonly IPermissionService _permissionService;
    protected readonly IStoreMappingService _storeMappingService;
    protected readonly IStoreService _storeService;

    #endregion

    #region Ctor

    public LanguageController(ICustomerActivityService customerActivityService,
        ILanguageModelFactory languageModelFactory,
        ILanguageService languageService,
        ILocalizationService localizationService,
        INopFileProvider fileProvider,
        INotificationService notificationService,
        IPermissionService permissionService,
        IStoreMappingService storeMappingService,
        IStoreService storeService)
    {
        _customerActivityService = customerActivityService;
        _languageModelFactory = languageModelFactory;
        _languageService = languageService;
        _localizationService = localizationService;
        _fileProvider = fileProvider;
        _notificationService = notificationService;
        _permissionService = permissionService;
        _storeMappingService = storeMappingService;
        _storeService = storeService;
    }

    #endregion

    #region Utilities

    protected virtual async Task SaveStoreMappingsAsync(Language language, LanguageModel model)
    {
        language.LimitedToStores = model.SelectedStoreIds.Any();
        await _languageService.UpdateLanguageAsync(language);

        var existingStoreMappings = await _storeMappingService.GetStoreMappingsAsync(language);
        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(language, store.Id);
            }
            else
            {
                //remove store
                var storeMappingToDelete = existingStoreMappings.FirstOrDefault(sm => sm.StoreId == store.Id);
                if (storeMappingToDelete != null)
                    await _storeMappingService.DeleteStoreMappingAsync(storeMappingToDelete);
            }
        }
    }

    #endregion

    #region Languages

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

    [CheckPermission(StandardPermission.Configuration.MANAGE_LANGUAGES)]
    public virtual async Task<IActionResult> List()
    {
        //prepare model
        var model = await _languageModelFactory.PrepareLanguageSearchModelAsync(new LanguageSearchModel());

        return View(model);
    }

    [HttpPost]
    [CheckPermission(StandardPermission.Configuration.MANAGE_LANGUAGES)]
    public virtual async Task<IActionResult> List(LanguageSearchModel searchModel)
    {
        //prepare model
        var model = await _languageModelFactory.PrepareLanguageListModelAsync(searchModel);

        return Json(model);
    }

    [CheckPermission(StandardPermission.Configuration.MANAGE_LANGUAGES)]
    public virtual async Task<IActionResult> Create()
    {
        //prepare model
        var model = await _languageModelFactory.PrepareLanguageModelAsync(new LanguageModel(), null);

        return View(model);
    }

    [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")]
    [CheckPermission(StandardPermission.Configuration.MANAGE_LANGUAGES)]
    public virtual async Task<IActionResult> Create(LanguageModel model, bool continueEditing)
    {
        if (ModelState.IsValid)
        {
            var language = model.ToEntity<Language>();
            await _languageService.InsertLanguageAsync(language);

            //activity log
            await _customerActivityService.InsertActivityAsync("AddNewLanguage",
                string.Format(await _localizationService.GetResourceAsync("ActivityLog.AddNewLanguage"), language.Id), language);

            //Stores
            await SaveStoreMappingsAsync(language, model);

            _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Configuration.Languages.Added"));
            _notificationService.WarningNotification(await _localizationService.GetResourceAsync("Admin.Configuration.Languages.NeedRestart"));

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

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

        //prepare model
        model = await _languageModelFactory.PrepareLanguageModelAsync(model, null, true);

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

    [CheckPermission(StandardPermission.Configuration.MANAGE_LANGUAGES)]
    public virtual async Task<IActionResult> Edit(int id)
    {
        //try to get a language with the specified id
        var language = await _languageService.GetLanguageByIdAsync(id);
        if (language == null)
            return RedirectToAction("List");

        //prepare model
        var model = await _languageModelFactory.PrepareLanguageModelAsync(null, language);

        return View(model);
    }

    [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")]
    [CheckPermission(StandardPermission.Configuration.MANAGE_LANGUAGES)]
    public virtual async Task<IActionResult> Edit(LanguageModel model, bool continueEditing)
    {
        //try to get a language with the specified id
        var language = await _languageService.GetLanguageByIdAsync(model.Id);
        if (language == null)
            return RedirectToAction("List");

        if (ModelState.IsValid)
        {
            //ensure we have at least one published language
            var allLanguages = await _languageService.GetAllLanguagesAsync();
            if (allLanguages.Count == 1 && allLanguages[0].Id == language.Id && !model.Published)
            {
                _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Configuration.Languages.PublishedLanguageRequired"));
                return RedirectToAction("Edit", new { id = language.Id });
            }

            //update
            language = model.ToEntity(language);
            await _languageService.UpdateLanguageAsync(language);

            //activity log
            await _customerActivityService.InsertActivityAsync("EditLanguage",
                string.Format(await _localizationService.GetResourceAsync("ActivityLog.EditLanguage"), language.Id), language);

            //Stores
            await SaveStoreMappingsAsync(language, model);

            //notification
            _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Configuration.Languages.Updated"));
            _notificationService.WarningNotification(await _localizationService.GetResourceAsync("Admin.Configuration.Languages.NeedRestart"));

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

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

        //prepare model
        model = await _languageModelFactory.PrepareLanguageModelAsync(model, language, true);

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

    [HttpPost]
    [CheckPermission(StandardPermission.Configuration.MANAGE_LANGUAGES)]
    public virtual async Task<IActionResult> Delete(int id)
    {
        //try to get a language with the specified id
        var language = await _languageService.GetLanguageByIdAsync(id);
        if (language == null)
            return RedirectToAction("List");

        //ensure we have at least one published language
        var allLanguages = await _languageService.GetAllLanguagesAsync();
        if (allLanguages.Count == 1 && allLanguages[0].Id == language.Id)
        {
            _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Configuration.Languages.PublishedLanguageRequired"));
            return RedirectToAction("Edit", new { id = language.Id });
        }

        //delete
        await _languageService.DeleteLanguageAsync(language);

        //activity log
        await _customerActivityService.InsertActivityAsync("DeleteLanguage",
            string.Format(await _localizationService.GetResourceAsync("ActivityLog.DeleteLanguage"), language.Id), language);

        //notification
        _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Configuration.Languages.Deleted"));
        _notificationService.WarningNotification(await _localizationService.GetResourceAsync("Admin.Configuration.Languages.NeedRestart"));

        return RedirectToAction("List");
    }

    [HttpPost]
    [CheckPermission(StandardPermission.Configuration.MANAGE_LANGUAGES)]
    public virtual JsonResult GetAvailableFlagFileNames()
    {
        var flagNames = _fileProvider
            .EnumerateFiles(_fileProvider.GetAbsolutePath(FLAGS_PATH), "*.png")
            .Select(_fileProvider.GetFileName)
            .ToList();

        var availableFlagFileNames = flagNames.Select(flagName => new SelectListItem
        {
            Text = flagName,
            Value = flagName
        }).ToList();

        return Json(availableFlagFileNames);
    }

    //action displaying notification (warning) to a store owner that changed culture
    public virtual async Task<IActionResult> LanguageCultureWarning(string currentCulture, string changedCulture)
    {
        if (currentCulture != changedCulture)
        {
            return Json(new
            {
                Result = string.Format(await _localizationService.GetResourceAsync("Admin.Configuration.Languages.CLDR.Warning"),
                    Url.Action("GeneralCommon", "Setting"))
            });
        }

        return Json(new { Result = string.Empty });
    }

    #endregion

    #region Resources

    [HttpPost]
    [CheckPermission(StandardPermission.Configuration.MANAGE_LANGUAGES)]
    public virtual async Task<IActionResult> Resources(LocaleResourceSearchModel searchModel)
    {
        //try to get a language with the specified id
        var language = await _languageService.GetLanguageByIdAsync(searchModel.LanguageId);
        if (language == null)
            return RedirectToAction("List");

        //prepare model
        var model = await _languageModelFactory.PrepareLocaleResourceListModelAsync(searchModel, language);

        return Json(model);
    }

    //ValidateAttribute is used to force model validation
    [HttpPost]
    [CheckPermission(StandardPermission.Configuration.MANAGE_LANGUAGES)]
    public virtual async Task<IActionResult> ResourceUpdate([Validate] LocaleResourceModel model)
    {
        if (!ModelState.IsValid)
        {
            return ErrorJson(ModelState.SerializeErrors());
        }

        var resource = await _localizationService.GetLocaleStringResourceByIdAsync(model.Id);
        // if the resourceName changed, ensure it isn't being used by another resource
        if (!resource.ResourceName.Equals(model.ResourceName, StringComparison.InvariantCultureIgnoreCase))
        {
            var res = await _localizationService.GetLocaleStringResourceByNameAsync(model.ResourceName, model.LanguageId, false);
            if (res != null && res.Id != resource.Id)
            {
                return ErrorJson(string.Format(await _localizationService.GetResourceAsync("Admin.Configuration.Languages.Resources.NameAlreadyExists"), res.ResourceName));
            }
        }

        //fill entity from model
        resource = model.ToEntity(resource);

        await _localizationService.UpdateLocaleStringResourceAsync(resource);

        return new NullJsonResult();
    }

    //ValidateAttribute is used to force model validation
    [HttpPost]
    [CheckPermission(StandardPermission.Configuration.MANAGE_LANGUAGES)]
    public virtual async Task<IActionResult> ResourceAdd(int languageId, [Validate] LocaleResourceModel model)
    {
        if (!ModelState.IsValid)
        {
            return ErrorJson(ModelState.SerializeErrors());
        }

        var res = await _localizationService.GetLocaleStringResourceByNameAsync(model.ResourceName, model.LanguageId, false);
        if (res == null)
        {
            //fill entity from model
            var resource = model.ToEntity<LocaleStringResource>();

            resource.LanguageId = languageId;

            await _localizationService.InsertLocaleStringResourceAsync(resource);
        }
        else
        {
            return ErrorJson(string.Format(await _localizationService.GetResourceAsync("Admin.Configuration.Languages.Resources.NameAlreadyExists"), model.ResourceName));
        }

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

    [HttpPost]
    [CheckPermission(StandardPermission.Configuration.MANAGE_LANGUAGES)]
    public virtual async Task<IActionResult> ResourceDelete(int id)
    {
        //try to get a locale resource with the specified id
        var resource = await _localizationService.GetLocaleStringResourceByIdAsync(id)
            ?? throw new ArgumentException("No resource found with the specified id", nameof(id));

        await _localizationService.DeleteLocaleStringResourceAsync(resource);

        return new NullJsonResult();
    }

    #endregion

    #region Export / Import

    [CheckPermission(StandardPermission.Configuration.MANAGE_LANGUAGES)]
    public virtual async Task<IActionResult> ExportXml(int id)
    {
        //try to get a language with the specified id
        var language = await _languageService.GetLanguageByIdAsync(id);
        if (language == null)
            return RedirectToAction("List");

        try
        {
            var xml = await _localizationService.ExportResourcesToXmlAsync(language);
            return File(Encoding.UTF8.GetBytes(xml), "application/xml", "language_pack.xml");
        }
        catch (Exception exc)
        {
            await _notificationService.ErrorNotificationAsync(exc);
            return RedirectToAction("List");
        }
    }

    [HttpPost]
    [CheckPermission(StandardPermission.Configuration.MANAGE_LANGUAGES)]
    public virtual async Task<IActionResult> ImportXml(int id, IFormFile importxmlfile)
    {
        //try to get a language with the specified id
        var language = await _languageService.GetLanguageByIdAsync(id);
        if (language == null)
            return RedirectToAction("List");

        try
        {
            if (importxmlfile != null && importxmlfile.Length > 0)
            {
                using var sr = new StreamReader(importxmlfile.OpenReadStream(), Encoding.UTF8);
                await _localizationService.ImportResourcesFromXmlAsync(language, sr);
            }
            else
            {
                _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Common.UploadFile"));
                return RedirectToAction("Edit", new { id = language.Id });
            }

            _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Configuration.Languages.Imported"));
            return RedirectToAction("Edit", new { id = language.Id });
        }
        catch (Exception exc)
        {
            await _notificationService.ErrorNotificationAsync(exc);
            return RedirectToAction("Edit", new { id = language.Id });
        }
    }

    #endregion
}