using Nop.Core;
using Nop.Core.Domain.Messages;
using Nop.Data;
using Nop.Services.Customers;

namespace Nop.Services.Messages;

/// <summary>
/// Campaign service
/// </summary>
public partial class CampaignService : ICampaignService
{
    #region Fields

    protected readonly ICustomerService _customerService;
    protected readonly IEmailSender _emailSender;
    protected readonly IMessageTokenProvider _messageTokenProvider;
    protected readonly IQueuedEmailService _queuedEmailService;
    protected readonly IRepository<Campaign> _campaignRepository;
    protected readonly IStoreContext _storeContext;
    protected readonly ITokenizer _tokenizer;

    #endregion

    #region Ctor

    public CampaignService(ICustomerService customerService,
        IEmailSender emailSender,
        IMessageTokenProvider messageTokenProvider,
        IQueuedEmailService queuedEmailService,
        IRepository<Campaign> campaignRepository,
        IStoreContext storeContext,
        ITokenizer tokenizer)
    {
        _customerService = customerService;
        _emailSender = emailSender;
        _messageTokenProvider = messageTokenProvider;
        _queuedEmailService = queuedEmailService;
        _campaignRepository = campaignRepository;
        _storeContext = storeContext;
        _tokenizer = tokenizer;
    }

    #endregion

    #region Methods

    /// <summary>
    /// Copies campaign
    /// </summary>
    /// <param name="campaign">Campaign to copy</param>
    /// <param name="newName">The new name of campaign copy</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the campaign copy
    /// </returns>
    public virtual async Task<Campaign> CopyCampaignAsync(Campaign campaign, string newName)
    {
        ArgumentNullException.ThrowIfNull(campaign);
        ArgumentException.ThrowIfNullOrEmpty(newName);

        var campaignCopy = new Campaign
        {
            Name = newName,
            Subject = campaign.Subject,
            Body = campaign.Body,
            StoreId = campaign.StoreId,
            CustomerRoleId = campaign.CustomerRoleId,
            NewsLetterSubscriptionTypeId = campaign.NewsLetterSubscriptionTypeId,
            CreatedOnUtc = DateTime.UtcNow
        };

        await _campaignRepository.InsertAsync(campaignCopy);

        return campaignCopy;
    }

    /// <summary>
    /// Inserts a campaign
    /// </summary>
    /// <param name="campaign">Campaign</param>        
    /// <returns>A task that represents the asynchronous operation</returns>
    public virtual async Task InsertCampaignAsync(Campaign campaign)
    {
        await _campaignRepository.InsertAsync(campaign);
    }

    /// <summary>
    /// Updates a campaign
    /// </summary>
    /// <param name="campaign">Campaign</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public virtual async Task UpdateCampaignAsync(Campaign campaign)
    {
        await _campaignRepository.UpdateAsync(campaign);
    }

    /// <summary>
    /// Deleted a queued email
    /// </summary>
    /// <param name="campaign">Campaign</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public virtual async Task DeleteCampaignAsync(Campaign campaign)
    {
        await _campaignRepository.DeleteAsync(campaign);
    }

    /// <summary>
    /// Gets a campaign by identifier
    /// </summary>
    /// <param name="campaignId">Campaign identifier</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the campaign
    /// </returns>
    public virtual async Task<Campaign> GetCampaignByIdAsync(int campaignId)
    {
        return await _campaignRepository.GetByIdAsync(campaignId, cache => default);
    }

    /// <summary>
    /// Gets all campaigns
    /// </summary>
    /// <param name="storeId">Store identifier; 0 to load all records</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the campaigns
    /// </returns>
    public virtual async Task<IList<Campaign>> GetAllCampaignsAsync(int storeId = 0)
    {
        var campaigns = await _campaignRepository.GetAllAsync(query =>
        {
            if (storeId > 0)
                query = query.Where(c => c.StoreId == storeId);

            query = query.OrderBy(c => c.CreatedOnUtc);

            return query;
        });

        return campaigns;
    }

    /// <summary>
    /// Sends a campaign to specified emails
    /// </summary>
    /// <param name="campaign">Campaign</param>
    /// <param name="emailAccount">Email account</param>
    /// <param name="subscriptions">Subscriptions</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the otal emails sent
    /// </returns>
    public virtual async Task<int> SendCampaignAsync(Campaign campaign, EmailAccount emailAccount,
        IEnumerable<NewsLetterSubscription> subscriptions)
    {
        ArgumentNullException.ThrowIfNull(campaign);

        ArgumentNullException.ThrowIfNull(emailAccount);

        var totalEmailsSent = 0;

        foreach (var subscription in subscriptions)
        {
            var customer = await _customerService.GetCustomerByEmailAsync(subscription.Email);
            //ignore deleted or inactive customers when sending newsletter campaigns
            if (customer != null && (!customer.Active || customer.Deleted))
                continue;

            var tokens = new List<Token>();
            await _messageTokenProvider.AddStoreTokensAsync(tokens, await _storeContext.GetCurrentStoreAsync(), emailAccount, subscription.LanguageId);
            await _messageTokenProvider.AddNewsLetterSubscriptionTokensAsync(tokens, subscription);
            if (customer != null)
                await _messageTokenProvider.AddCustomerTokensAsync(tokens, customer);

            var subject = _tokenizer.Replace(campaign.Subject, tokens, false);
            var body = _tokenizer.Replace(campaign.Body, tokens, true);

            var email = new QueuedEmail
            {
                Priority = QueuedEmailPriority.Low,
                From = emailAccount.Email,
                FromName = emailAccount.DisplayName,
                To = subscription.Email,
                Subject = subject,
                Body = body,
                CreatedOnUtc = DateTime.UtcNow,
                EmailAccountId = emailAccount.Id,
                DontSendBeforeDateUtc = campaign.DontSendBeforeDateUtc
            };
            await _queuedEmailService.InsertQueuedEmailAsync(email);
            totalEmailsSent++;
        }

        return totalEmailsSent;
    }

    /// <summary>
    /// Sends a campaign to specified email
    /// </summary>
    /// <param name="campaign">Campaign</param>
    /// <param name="emailAccount">Email account</param>
    /// <param name="email">Email</param>
    /// <param name="languageId">Language identifier</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public virtual async Task SendCampaignAsync(Campaign campaign, EmailAccount emailAccount, string email, int languageId)
    {
        ArgumentNullException.ThrowIfNull(campaign);

        ArgumentNullException.ThrowIfNull(emailAccount);

        var tokens = new List<Token>();
        await _messageTokenProvider.AddStoreTokensAsync(tokens, await _storeContext.GetCurrentStoreAsync(), emailAccount, languageId);
        var customer = await _customerService.GetCustomerByEmailAsync(email);
        if (customer != null)
            await _messageTokenProvider.AddCustomerTokensAsync(tokens, customer);

        var subject = _tokenizer.Replace(campaign.Subject, tokens, false);
        var body = _tokenizer.Replace(campaign.Body, tokens, true);

        await _emailSender.SendEmailAsync(emailAccount, subject, body, emailAccount.Email, emailAccount.DisplayName, email, null);
    }

    #endregion
}