using Meezi.API.Models.Crm;
using Meezi.Core.Constants;
using Meezi.Core.Enums;
using Meezi.Core.Interfaces;
using Meezi.Core.Utilities;
using Meezi.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using StackExchange.Redis;

namespace Meezi.API.Services;

public interface ISmsMarketingService
{
    Task<SmsUsageDto> GetUsageAsync(string cafeId, PlanTier planTier, CancellationToken cancellationToken = default);
    Task<(bool Success, SmsCampaignResult? Data, string? ErrorCode, string? Message)> SendCampaignAsync(
        string cafeId,
        PlanTier planTier,
        SendSmsCampaignRequest request,
        CancellationToken cancellationToken = default);
}

public class SmsMarketingService : ISmsMarketingService
{
    private readonly AppDbContext _db;
    private readonly ISmsService _smsService;
    private readonly IConnectionMultiplexer _redis;

    public SmsMarketingService(
        AppDbContext db,
        ISmsService smsService,
        IConnectionMultiplexer redis)
    {
        _db = db;
        _smsService = smsService;
        _redis = redis;
    }

    public async Task<SmsUsageDto> GetUsageAsync(
        string cafeId,
        PlanTier planTier,
        CancellationToken cancellationToken = default)
    {
        var month = DateTime.UtcNow.ToString("yyyy-MM");
        var used = await GetUsedCountAsync(cafeId, month);
        var limit = PlanLimits.MaxSmsPerMonth(planTier);
        return new SmsUsageDto(used, limit == int.MaxValue ? -1 : limit, month);
    }

    public async Task<(bool Success, SmsCampaignResult? Data, string? ErrorCode, string? Message)> SendCampaignAsync(
        string cafeId,
        PlanTier planTier,
        SendSmsCampaignRequest request,
        CancellationToken cancellationToken = default)
    {
        var maxSms = PlanLimits.MaxSmsPerMonth(planTier);
        if (maxSms == 0)
            return (false, null, "PLAN_LIMIT_REACHED", "SMS is not available on the Free plan.");

        var phones = await ResolvePhonesAsync(cafeId, request, cancellationToken);
        if (phones.Count == 0)
            return (false, null, "NOT_FOUND", "No recipients found.");

        var month = DateTime.UtcNow.ToString("yyyy-MM");
        var used = await GetUsedCountAsync(cafeId, month);
        if (maxSms != int.MaxValue && used + phones.Count > maxSms)
            return (false, null, "PLAN_LIMIT_REACHED", "Monthly SMS limit would be exceeded.");

        var result = await _smsService.SendBulkAsync(phones, request.Message, cancellationToken);

        if (result.SentCount > 0)
            await IncrementUsageAsync(cafeId, month, result.SentCount);

        return (true, new SmsCampaignResult(result.SentCount, result.FailedCount), null, null);
    }

    private async Task<List<string>> ResolvePhonesAsync(
        string cafeId,
        SendSmsCampaignRequest request,
        CancellationToken cancellationToken)
    {
        if (request.Phones is { Count: > 0 })
        {
            return request.Phones
                .Select(PhoneNormalizer.Normalize)
                .Where(PhoneNormalizer.IsValidIranMobile)
                .Distinct()
                .ToList();
        }

        var query = _db.Customers.Where(c => c.CafeId == cafeId);
        if (request.TargetGroup.HasValue)
            query = query.Where(c => c.Group == request.TargetGroup.Value);

        return await query.Select(c => c.Phone).Distinct().ToListAsync(cancellationToken);
    }

    private async Task<int> GetUsedCountAsync(string cafeId, string month)
    {
        var redis = _redis.GetDatabase();
        var value = await redis.StringGetAsync(UsageKey(cafeId, month));
        return value.HasValue ? (int)value : 0;
    }

    private async Task IncrementUsageAsync(string cafeId, string month, int count)
    {
        var redis = _redis.GetDatabase();
        var key = UsageKey(cafeId, month);
        await redis.StringIncrementAsync(key, count);
        await redis.KeyExpireAsync(key, TimeSpan.FromDays(40));
    }

    private static string UsageKey(string cafeId, string month) => $"sms:usage:{cafeId}:{month}";
}
