using Meezi.API.Models.Crm;
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, CancellationToken cancellationToken = default);
    Task<SmsSettingsDto> GetSettingsAsync(string cafeId, CancellationToken cancellationToken = default);
    Task<(bool Success, SmsSettingsDto? Data, string? ErrorCode, string? Message)> UpdateSettingsAsync(
        string cafeId,
        UpdateSmsSettingsRequest request,
        CancellationToken cancellationToken = default);
    Task<SmsBalanceDto> GetBalanceAsync(string cafeId, CancellationToken cancellationToken = default);
    Task<(bool Success, SmsCampaignResult? Data, string? ErrorCode, string? Message)> SendCampaignAsync(
        string cafeId,
        SendSmsCampaignRequest request,
        CancellationToken cancellationToken = default);
}

/// <summary>
/// Marketing SMS is bring-your-own-provider: each café configures its OWN
/// Kavenegar API key + sender line and pays its provider directly. The platform
/// neither sells SMS nor meters it against plan limits; the monthly counter is
/// informational only. (Login OTPs still go through the platform account.)
/// </summary>
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, CancellationToken cancellationToken = default)
    {
        var month = DateTime.UtcNow.ToString("yyyy-MM");
        var used = await GetUsedCountAsync(cafeId, month);
        // -1 = no platform limit; the café's own provider account is the only cap.
        return new SmsUsageDto(used, -1, month);
    }

    public async Task<SmsSettingsDto> GetSettingsAsync(string cafeId, CancellationToken cancellationToken = default)
    {
        var cafe = await _db.Cafes.AsNoTracking()
            .Where(c => c.Id == cafeId)
            .Select(c => new { c.SmsApiKey, c.SmsSenderNumber })
            .FirstOrDefaultAsync(cancellationToken);

        if (cafe is null || string.IsNullOrWhiteSpace(cafe.SmsApiKey))
            return new SmsSettingsDto(false, null, cafe?.SmsSenderNumber);

        return new SmsSettingsDto(true, MaskApiKey(cafe.SmsApiKey), cafe.SmsSenderNumber);
    }

    public async Task<(bool Success, SmsSettingsDto? Data, string? ErrorCode, string? Message)> UpdateSettingsAsync(
        string cafeId,
        UpdateSmsSettingsRequest request,
        CancellationToken cancellationToken = default)
    {
        var cafe = await _db.Cafes.FirstOrDefaultAsync(c => c.Id == cafeId, cancellationToken);
        if (cafe is null)
            return (false, null, "NOT_FOUND", "Cafe not found.");

        var apiKey = request.ApiKey?.Trim();
        var sender = request.SenderNumber?.Trim();

        // Empty strings clear the configuration (turn SMS off for this café).
        if (string.IsNullOrEmpty(apiKey) && string.IsNullOrEmpty(sender))
        {
            cafe.SmsApiKey = null;
            cafe.SmsSenderNumber = null;
            await _db.SaveChangesAsync(cancellationToken);
            return (true, new SmsSettingsDto(false, null, null), null, null);
        }

        if (string.IsNullOrEmpty(apiKey) && string.IsNullOrEmpty(cafe.SmsApiKey))
            return (false, null, "VALIDATION_ERROR", "API key is required.");
        if (string.IsNullOrEmpty(sender))
            return (false, null, "VALIDATION_ERROR", "Sender number is required.");

        // A new key was provided — verify it against the provider before saving so
        // the owner gets immediate feedback on a typo'd key.
        if (!string.IsNullOrEmpty(apiKey))
        {
            var info = await _smsService.GetAccountInfoAsync(apiKey, cancellationToken);
            if (info is null)
                return (false, null, "SMS_KEY_INVALID", "The API key was rejected by the SMS provider.");
            cafe.SmsApiKey = apiKey;
        }

        cafe.SmsSenderNumber = sender;
        await _db.SaveChangesAsync(cancellationToken);

        return (true, new SmsSettingsDto(true, MaskApiKey(cafe.SmsApiKey!), cafe.SmsSenderNumber), null, null);
    }

    public async Task<SmsBalanceDto> GetBalanceAsync(string cafeId, CancellationToken cancellationToken = default)
    {
        var apiKey = await _db.Cafes.AsNoTracking()
            .Where(c => c.Id == cafeId)
            .Select(c => c.SmsApiKey)
            .FirstOrDefaultAsync(cancellationToken);

        if (string.IsNullOrWhiteSpace(apiKey))
            return new SmsBalanceDto(0, "master", false);

        var info = await _smsService.GetAccountInfoAsync(apiKey, cancellationToken);
        return info is not null
            ? new SmsBalanceDto(info.RemainCredit, info.AccountType, true)
            : new SmsBalanceDto(0, "master", false);
    }

    public async Task<(bool Success, SmsCampaignResult? Data, string? ErrorCode, string? Message)> SendCampaignAsync(
        string cafeId,
        SendSmsCampaignRequest request,
        CancellationToken cancellationToken = default)
    {
        var cafe = await _db.Cafes.AsNoTracking()
            .Where(c => c.Id == cafeId)
            .Select(c => new { c.SmsApiKey, c.SmsSenderNumber })
            .FirstOrDefaultAsync(cancellationToken);

        if (cafe is null || string.IsNullOrWhiteSpace(cafe.SmsApiKey) || string.IsNullOrWhiteSpace(cafe.SmsSenderNumber))
            return (false, null, "SMS_NOT_CONFIGURED",
                "Configure your own SMS provider (API key + sender line) in the SMS settings first.");

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

        var result = await _smsService.SendBulkWithCredentialsAsync(
            cafe.SmsApiKey, cafe.SmsSenderNumber, phones, request.Message, cancellationToken);

        if (result.SentCount > 0)
        {
            var month = DateTime.UtcNow.ToString("yyyy-MM");
            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 static string MaskApiKey(string apiKey) =>
        apiKey.Length <= 4 ? "****" : $"****{apiKey[^4..]}";

    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}";
}
