using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Responses;
using Google.Apis.Auth.OAuth2.Web;
using Google.Apis.Util.Store;
using Microsoft.AspNetCore.Mvc;
using Nop.Core;
using Nop.Core.Domain.Messages;
using Nop.Core.Infrastructure;
using Nop.Services.Configuration;
using Nop.Services.Localization;
using Nop.Services.Logging;
using Nop.Services.Messages;
using Nop.Services.Security;
using Nop.Web.Areas.Admin.Factories;
using Nop.Web.Areas.Admin.Infrastructure.Mapper.Extensions;
using Nop.Web.Areas.Admin.Models.Messages;
using Nop.Web.Framework.Controllers;
using Nop.Web.Framework.Mvc.Filters;

namespace Nop.Web.Areas.Admin.Controllers;

public partial class EmailAccountController : BaseAdminController
{
    #region Fields

    private const char SEPARATOR = '-';

    protected readonly EmailAccountSettings _emailAccountSettings;
    protected readonly ICustomerActivityService _customerActivityService;
    protected readonly IEmailAccountModelFactory _emailAccountModelFactory;
    protected readonly IEmailAccountService _emailAccountService;
    protected readonly IEmailSender _emailSender;
    protected readonly ILocalizationService _localizationService;
    protected readonly INopFileProvider _fileProvider;
    protected readonly INotificationService _notificationService;
    protected readonly IPermissionService _permissionService;
    protected readonly ISettingService _settingService;
    protected readonly IStoreContext _storeContext;
    protected readonly IWebHelper _webHelper;

    #endregion

    #region Ctor

    public EmailAccountController(EmailAccountSettings emailAccountSettings,
        ICustomerActivityService customerActivityService,
        IEmailAccountModelFactory emailAccountModelFactory,
        IEmailAccountService emailAccountService,
        IEmailSender emailSender,
        ILocalizationService localizationService,
        INopFileProvider fileProvider,
        INotificationService notificationService,
        IPermissionService permissionService,
        ISettingService settingService,
        IStoreContext storeContext,
        IWebHelper webHelper)
    {
        _emailAccountSettings = emailAccountSettings;
        _customerActivityService = customerActivityService;
        _emailAccountModelFactory = emailAccountModelFactory;
        _emailAccountService = emailAccountService;
        _emailSender = emailSender;
        _localizationService = localizationService;
        _fileProvider = fileProvider;
        _notificationService = notificationService;
        _permissionService = permissionService;
        _settingService = settingService;
        _storeContext = storeContext;
        _webHelper = webHelper;
    }

    #endregion

    #region Utilities

    protected virtual async Task<string> PrepareOAuthUrlAsync(EmailAccount emailAccount)
    {
        if (string.IsNullOrEmpty(emailAccount.ClientSecret) || string.IsNullOrEmpty(emailAccount.ClientId))
        {
            ModelState.AddModelError(nameof(EmailAccountModel.ClientSecret), await _localizationService.GetResourceAsync("Admin.Configuration.EmailAccounts.Fields.ClientSecret.Required"));
            return string.Empty;
        }

        var tokenFilePath = _fileProvider.MapPath(NopMessageDefaults.GmailAuthStorePath);
        var credentialRoot = _fileProvider.Combine(tokenFilePath, emailAccount.Email);

        var codeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
        {
            ClientSecrets = new ClientSecrets
            {
                ClientId = emailAccount.ClientId,
                ClientSecret = emailAccount.ClientSecret
            },
            Scopes = NopMessageDefaults.GmailScopes,
            DataStore = new FileDataStore(credentialRoot, true),
            UserDefinedQueryParams = new Dictionary<string, string> { ["emailAccountId"] = emailAccount.Id.ToString() }
        });

        var redirectUri = Url.Action(nameof(AuthReturn), null, null, _webHelper.GetCurrentRequestProtocol());
        var authCode = new AuthorizationCodeWebApp(codeFlow, redirectUri, $"{emailAccount.Id}{SEPARATOR}");
        var authResult = await authCode.AuthorizeAsync(emailAccount.Email, CancellationToken.None);

        return authResult.RedirectUri?.ToString();
    }

    #endregion

    #region Methods

    [CheckPermission(StandardPermission.Configuration.MANAGE_EMAIL_ACCOUNTS)]
    public virtual async Task<IActionResult> List()
    {
        //prepare model
        var model = await _emailAccountModelFactory.PrepareEmailAccountSearchModelAsync(new EmailAccountSearchModel());

        return View(model);
    }

    [HttpPost]
    [CheckPermission(StandardPermission.Configuration.MANAGE_EMAIL_ACCOUNTS)]
    public virtual async Task<IActionResult> List(EmailAccountSearchModel searchModel)
    {
        //prepare model
        var model = await _emailAccountModelFactory.PrepareEmailAccountListModelAsync(searchModel);

        return Json(model);
    }

    [CheckPermission(StandardPermission.Configuration.MANAGE_EMAIL_ACCOUNTS)]
    public virtual async Task<IActionResult> MarkAsDefaultEmail(int id)
    {
        var defaultEmailAccount = await _emailAccountService.GetEmailAccountByIdAsync(id);
        if (defaultEmailAccount == null)
            return RedirectToAction("List");

        _emailAccountSettings.DefaultEmailAccountId = defaultEmailAccount.Id;
        await _settingService.SaveSettingAsync(_emailAccountSettings);

        return RedirectToAction("List");
    }

    [CheckPermission(StandardPermission.Configuration.MANAGE_EMAIL_ACCOUNTS)]
    public virtual async Task<IActionResult> Create()
    {
        //prepare model
        var model = await _emailAccountModelFactory.PrepareEmailAccountModelAsync(new EmailAccountModel(), null);

        return View(model);
    }

    [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")]
    [CheckPermission(StandardPermission.Configuration.MANAGE_EMAIL_ACCOUNTS)]
    public virtual async Task<IActionResult> Create(EmailAccountModel model, bool continueEditing)
    {
        if (ModelState.IsValid)
        {
            var emailAccount = model.ToEntity<EmailAccount>();

            //set password manually
            emailAccount.Password = model.Password;
            await _emailAccountService.InsertEmailAccountAsync(emailAccount);

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

            _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Configuration.EmailAccounts.Added"));

            return continueEditing ? RedirectToAction("Edit", new { id = emailAccount.Id }) : RedirectToAction("List");
        }

        //prepare model
        model = await _emailAccountModelFactory.PrepareEmailAccountModelAsync(model, null, true);

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

    [CheckPermission(StandardPermission.Configuration.MANAGE_EMAIL_ACCOUNTS)]
    public virtual async Task<IActionResult> Edit(int id)
    {
        //try to get an email account with the specified id
        var emailAccount = await _emailAccountService.GetEmailAccountByIdAsync(id);
        if (emailAccount == null)
            return RedirectToAction("List");

        //prepare model
        var model = await _emailAccountModelFactory.PrepareEmailAccountModelAsync(null, emailAccount);

        if (emailAccount.EmailAuthenticationMethod == EmailAuthenticationMethod.GmailOAuth2)
            model.AuthUrl = await PrepareOAuthUrlAsync(emailAccount);

        return View(model);
    }

    [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")]
    [FormValueRequired("save", "save-continue")]
    [CheckPermission(StandardPermission.Configuration.MANAGE_EMAIL_ACCOUNTS)]
    public virtual async Task<IActionResult> Edit(EmailAccountModel model, bool continueEditing)
    {
        //try to get an email account with the specified id
        var emailAccount = await _emailAccountService.GetEmailAccountByIdAsync(model.Id);
        if (emailAccount == null)
            return RedirectToAction("List");

        if (ModelState.IsValid)
        {
            emailAccount = model.ToEntity(emailAccount);
            await _emailAccountService.UpdateEmailAccountAsync(emailAccount);

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

            _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Configuration.EmailAccounts.Updated"));

            return continueEditing ? RedirectToAction("Edit", new { id = emailAccount.Id }) : RedirectToAction("List");
        }

        //prepare model
        model = await _emailAccountModelFactory.PrepareEmailAccountModelAsync(model, emailAccount, true);

        if (emailAccount.EmailAuthenticationMethod == EmailAuthenticationMethod.GmailOAuth2)
            model.AuthUrl = await PrepareOAuthUrlAsync(emailAccount);

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

    [HttpPost, ActionName("Edit")]
    [FormValueRequired("changesecret")]
    [CheckPermission(StandardPermission.Configuration.MANAGE_EMAIL_ACCOUNTS)]
    public virtual async Task<IActionResult> ChangeSecret(EmailAccountModel model)
    {
        //try to get an email account with the specified id
        var emailAccount = await _emailAccountService.GetEmailAccountByIdAsync(model.Id);
        if (emailAccount == null)
            return RedirectToAction("List");

        //do not validate model
        emailAccount.ClientSecret = model.ClientSecret;
        await _emailAccountService.UpdateEmailAccountAsync(emailAccount);

        _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Configuration.EmailAccounts.Fields.ClientSecret.ClientSecretChanged"));

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

    [HttpPost, ActionName("Edit")]
    [FormValueRequired("changepassword")]
    [CheckPermission(StandardPermission.Configuration.MANAGE_EMAIL_ACCOUNTS)]
    public virtual async Task<IActionResult> ChangePassword(EmailAccountModel model)
    {
        //try to get an email account with the specified id
        var emailAccount = await _emailAccountService.GetEmailAccountByIdAsync(model.Id);
        if (emailAccount == null)
            return RedirectToAction("List");

        //do not validate model
        emailAccount.Password = model.Password;
        await _emailAccountService.UpdateEmailAccountAsync(emailAccount);

        _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Configuration.EmailAccounts.Fields.Password.PasswordChanged"));

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

    [HttpPost, ActionName("Edit")]
    [FormValueRequired("sendtestemail")]
    [CheckPermission(StandardPermission.Configuration.MANAGE_EMAIL_ACCOUNTS)]
    public virtual async Task<IActionResult> SendTestEmail(EmailAccountModel model)
    {
        //try to get an email account with the specified id
        var emailAccount = await _emailAccountService.GetEmailAccountByIdAsync(model.Id);
        if (emailAccount == null)
            return RedirectToAction("List");

        if (!CommonHelper.IsValidEmail(model.SendTestEmailTo))
        {
            _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Common.WrongEmail"));
            return View(await _emailAccountModelFactory.PrepareEmailAccountModelAsync(model, emailAccount, true));
        }

        try
        {
            if (string.IsNullOrWhiteSpace(model.SendTestEmailTo))
                throw new NopException("Enter test email address");
            var store = await _storeContext.GetCurrentStoreAsync();
            var subject = store.Name + ". Testing email functionality.";
            var body = "Email works fine.";
            await _emailSender.SendEmailAsync(emailAccount, subject, body, emailAccount.Email, emailAccount.DisplayName, model.SendTestEmailTo, null);

            _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Configuration.EmailAccounts.SendTestEmail.Success"));

            return RedirectToAction("Edit", new { id = emailAccount.Id });
        }
        catch (Exception exc)
        {
            _notificationService.ErrorNotification(exc.Message);
        }

        //prepare model
        model = await _emailAccountModelFactory.PrepareEmailAccountModelAsync(model, emailAccount, true);

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

    [HttpPost]
    [CheckPermission(StandardPermission.Configuration.MANAGE_EMAIL_ACCOUNTS)]
    public virtual async Task<IActionResult> Delete(int id)
    {
        //try to get an email account with the specified id
        var emailAccount = await _emailAccountService.GetEmailAccountByIdAsync(id);
        if (emailAccount == null)
            return RedirectToAction("List");

        try
        {
            await _emailAccountService.DeleteEmailAccountAsync(emailAccount);

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

            _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Configuration.EmailAccounts.Deleted"));

            return RedirectToAction("List");
        }
        catch (Exception exc)
        {
            await _notificationService.ErrorNotificationAsync(exc);
            return RedirectToAction("Edit", new { id = emailAccount.Id });
        }
    }

    public virtual async Task<IActionResult> AuthReturn(AuthorizationCodeResponseUrl authorizationCode)
    {
        if (string.IsNullOrEmpty(authorizationCode.State))
            return RedirectToAction(nameof(List));

        var strAccountId = authorizationCode.State.Split(SEPARATOR).FirstOrDefault();

        if (!int.TryParse(strAccountId, out var accountId) ||
            await _emailAccountService.GetEmailAccountByIdAsync(accountId) is not EmailAccount emailAccount)
        {
            _notificationService.ErrorNotification("Email account could not be loaded");
            return RedirectToAction(nameof(List));
        }

        if (!string.IsNullOrEmpty(authorizationCode.Error))
        {
            _notificationService.ErrorNotification(authorizationCode.Error);
            return RedirectToAction(nameof(Edit), new { id = emailAccount.Id });
        }

        if (string.IsNullOrEmpty(authorizationCode?.Code))
            return RedirectToAction(nameof(Edit), new { id = emailAccount.Id });

        var tokenFilePath = _fileProvider.MapPath(NopMessageDefaults.GmailAuthStorePath);
        var credentialRoot = _fileProvider.Combine(tokenFilePath, emailAccount.Email);

        var flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
        {
            ClientSecrets = new ClientSecrets
            {
                ClientId = emailAccount.ClientId,
                ClientSecret = emailAccount.ClientSecret
            },
            Scopes = NopMessageDefaults.GmailScopes,
            DataStore = new FileDataStore(credentialRoot, true)
        });

        var redirectUri = Url.Action(nameof(AuthReturn), null, null, _webHelper.GetCurrentRequestProtocol());
        var tokenResponse = await flow.ExchangeCodeForTokenAsync(emailAccount.Email, authorizationCode.Code, redirectUri, CancellationToken.None);

        if (tokenResponse is null || tokenResponse.IsStale)
            return RedirectToAction(nameof(Edit), new { id = emailAccount.Id });

        _notificationService.SuccessNotification("The token was successfully retrieved from the server");

        return RedirectToAction(nameof(Edit), new { id = emailAccount.Id });
    }

    #endregion
}