using System.Collections.Concurrent;
using System.Threading.Channels;
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace Esim.SendMail.Services;

/// <summary>
/// High-performance email service optimized for millions of messages
/// Features: Connection pooling, rate limiting, exponential backoff
/// </summary>
public interface IEmailService
{
    Task<bool> SendEmailAsync(string to, string subject, string body, bool isHtml = true);
    Task<int> SendBatchAsync(IEnumerable<EmailMessage> messages, CancellationToken cancellationToken = default);
    int CalculateBackoffDelay(int retryCount);
    EmailMetrics GetMetrics();
    void Dispose();
}

public class EmailMessage
{
    public decimal Id { get; set; }
    public string To { get; set; } = null!;
    public string Subject { get; set; } = null!;
    public string Body { get; set; } = null!;
    public bool IsHtml { get; set; } = true;
}

public class EmailResult
{
    public decimal MessageId { get; set; }
    public bool Success { get; set; }
    public string? ErrorMessage { get; set; }
    public bool IsRetryable { get; set; }
}

public class EmailMetrics
{
    public long TotalSent { get; set; }
    public long TotalSuccess { get; set; }
    public long TotalFailed { get; set; }
    public long TotalRetried { get; set; }
    public double AverageLatencyMs { get; set; }
    public int EmailsLastMinute { get; set; }
    public DateTime LastSentAt { get; set; }
}

public class HighPerformanceEmailService : IEmailService, IDisposable
{
    private readonly ILogger<HighPerformanceEmailService> _logger;
    private readonly IConfiguration _configuration;
    private readonly string _smtpServer;
    private readonly int _smtpPort;
    private readonly string _senderEmail;
    private readonly string _senderName;
    private readonly string _senderPassword;
    private readonly bool _enableSsl;
    private readonly int _connectionPoolSize;
    private readonly int _maxConcurrentSends;
    private readonly int _maxEmailsPerMinute;
    private readonly int _baseRetryDelayMs;
    private readonly int _maxRetryDelayMs;

    // Connection pool for SMTP clients
    private readonly ConcurrentBag<SmtpClient> _connectionPool;
    private readonly SemaphoreSlim _poolSemaphore;
    
    // Rate limiting
    private readonly SemaphoreSlim _rateLimitSemaphore;
    private readonly ConcurrentQueue<DateTime> _sentTimestamps;
    private readonly object _rateLimitLock = new();

    // Metrics tracking
    private long _totalSent;
    private long _totalSuccess;
    private long _totalFailed;
    private long _totalRetried;
    private long _totalLatencyMs;
    private DateTime _lastSentAt;

    private bool _disposed = false;

    public HighPerformanceEmailService(ILogger<HighPerformanceEmailService> logger, IConfiguration configuration)
    {
        _logger = logger;
        _configuration = configuration;
        
        // SMTP configuration
        _smtpServer = _configuration["Email:SmtpServer"] ?? "smtp.gmail.com";
        _smtpPort = int.Parse(_configuration["Email:SmtpPort"] ?? "587");
        _senderEmail = _configuration["Email:SenderEmail"] ?? "";
        _senderName = _configuration["Email:SenderName"] ?? "EsimLao";
        _senderPassword = _configuration["Email:SenderPassword"] ?? "";
        _enableSsl = bool.Parse(_configuration["Email:EnableSsl"] ?? "true");
        
        // Performance configuration
        _connectionPoolSize = int.Parse(_configuration["Email:ConnectionPoolSize"] ?? "5");
        _maxConcurrentSends = int.Parse(_configuration["Email:MaxConcurrentSends"] ?? "10");
        _maxEmailsPerMinute = int.Parse(_configuration["Email:MaxEmailsPerMinute"] ?? "30");
        _baseRetryDelayMs = int.Parse(_configuration["Email:BaseRetryDelayMs"] ?? "2000");
        _maxRetryDelayMs = int.Parse(_configuration["Email:MaxRetryDelayMs"] ?? "60000");

        // Initialize pools
        _connectionPool = new ConcurrentBag<SmtpClient>();
        _poolSemaphore = new SemaphoreSlim(_connectionPoolSize, _connectionPoolSize);
        
        // Initialize rate limiting
        _rateLimitSemaphore = new SemaphoreSlim(_maxConcurrentSends, _maxConcurrentSends);
        _sentTimestamps = new ConcurrentQueue<DateTime>();

        // Pre-initialize connection pool
        InitializeConnectionPool();
        
        _logger.LogInformation(
            "EmailService initialized: Pool={PoolSize}, Concurrent={Concurrent}, RateLimit={RateLimit}/min",
            _connectionPoolSize, _maxConcurrentSends, _maxEmailsPerMinute);
    }

    private void InitializeConnectionPool()
    {
        _logger.LogInformation("Initializing SMTP connection pool with {PoolSize} connections", _connectionPoolSize);
        
        for (int i = 0; i < _connectionPoolSize; i++)
        {
            try
            {
                var client = CreateConnectedClient();
                if (client != null)
                {
                    _connectionPool.Add(client);
                }
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Failed to create initial SMTP connection {Index}: {Message}", i + 1, ex.Message);
            }
        }
        
        _logger.LogInformation("Connection pool initialized with {Count} connections", _connectionPool.Count);
    }

    private SmtpClient? CreateConnectedClient()
    {
        try
        {
            var client = new SmtpClient();
            client.Timeout = 30000; // 30 seconds
            client.Connect(_smtpServer, _smtpPort, _enableSsl ? SecureSocketOptions.StartTls : SecureSocketOptions.None);
            client.Authenticate(_senderEmail, _senderPassword);
            return client;
        }
        catch (Exception ex)
        {
            _logger.LogError("Failed to create SMTP connection: {Message}", ex.Message);
            return null;
        }
    }

    private async Task<SmtpClient?> GetClientFromPoolAsync(CancellationToken cancellationToken = default)
    {
        await _poolSemaphore.WaitAsync(cancellationToken);

        if (_connectionPool.TryTake(out var client))
        {
            if (client.IsConnected)
            {
                return client;
            }
            
            try { client.Dispose(); } catch { }
        }

        return CreateConnectedClient();
    }

    private void ReturnClientToPool(SmtpClient? client)
    {
        if (client != null && client.IsConnected)
        {
            _connectionPool.Add(client);
        }
        else
        {
            try
            {
                var newClient = CreateConnectedClient();
                if (newClient != null)
                {
                    _connectionPool.Add(newClient);
                }
            }
            catch { }
        }
        
        _poolSemaphore.Release();
    }

    /// <summary>
    /// Apply rate limiting - waits if we've exceeded emails per minute
    /// </summary>
    private async Task ApplyRateLimitAsync(CancellationToken cancellationToken = default)
    {
        var now = DateTime.UtcNow;
        var oneMinuteAgo = now.AddMinutes(-1);

        // Clean up old timestamps
        while (_sentTimestamps.TryPeek(out var oldestTime) && oldestTime < oneMinuteAgo)
        {
            _sentTimestamps.TryDequeue(out _);
        }

        // Check if we need to wait
        while (_sentTimestamps.Count >= _maxEmailsPerMinute)
        {
            if (_sentTimestamps.TryPeek(out var oldestTime))
            {
                var waitTime = oldestTime.AddMinutes(1) - DateTime.UtcNow;
                if (waitTime > TimeSpan.Zero)
                {
                    _logger.LogDebug("Rate limit reached, waiting {WaitMs}ms", waitTime.TotalMilliseconds);
                    await Task.Delay(waitTime, cancellationToken);
                }
            }

            // Clean up again
            now = DateTime.UtcNow;
            oneMinuteAgo = now.AddMinutes(-1);
            while (_sentTimestamps.TryPeek(out var time) && time < oneMinuteAgo)
            {
                _sentTimestamps.TryDequeue(out _);
            }
        }

        // Record this send
        _sentTimestamps.Enqueue(DateTime.UtcNow);
    }

    /// <summary>
    /// Calculate exponential backoff delay
    /// </summary>
    public int CalculateBackoffDelay(int retryCount)
    {
        var delay = _baseRetryDelayMs * (int)Math.Pow(2, retryCount);
        return Math.Min(delay, _maxRetryDelayMs);
    }

    /// <summary>
    /// Determine if an error is retryable
    /// </summary>
    private static bool IsRetryableError(Exception ex)
    {
        // Network errors are retryable
        if (ex is System.Net.Sockets.SocketException) return true;
        if (ex is TimeoutException) return true;
        if (ex is OperationCanceledException) return false;
        
        // SMTP errors
        if (ex is SmtpCommandException smtpEx)
        {
            // 4xx errors are temporary, 5xx are permanent
            return smtpEx.StatusCode < MailKit.Net.Smtp.SmtpStatusCode.MailboxNameNotAllowed;
        }
        
        return true; // Default to retryable
    }

    public async Task<bool> SendEmailAsync(string to, string subject, string body, bool isHtml = true)
    {
        var startTime = DateTime.UtcNow;
        SmtpClient? client = null;
        
        try
        {
            // Apply rate limiting
            await ApplyRateLimitAsync();
            
            // Wait for concurrent send slot
            await _rateLimitSemaphore.WaitAsync();
            
            try
            {
                client = await GetClientFromPoolAsync();
                if (client == null)
                {
                    _logger.LogError("Failed to get SMTP client from pool for {To}", to);
                    Interlocked.Increment(ref _totalFailed);
                    return false;
                }

                var message = CreateMimeMessage(to, subject, body, isHtml);
                await client.SendAsync(message);
                
                // Update metrics
                Interlocked.Increment(ref _totalSent);
                Interlocked.Increment(ref _totalSuccess);
                Interlocked.Add(ref _totalLatencyMs, (long)(DateTime.UtcNow - startTime).TotalMilliseconds);
                _lastSentAt = DateTime.UtcNow;
                
                _logger.LogDebug("Email sent to {To} in {ElapsedMs}ms", to, (DateTime.UtcNow - startTime).TotalMilliseconds);
                return true;
            }
            finally
            {
                _rateLimitSemaphore.Release();
            }
        }
        catch (Exception ex)
        {
            Interlocked.Increment(ref _totalSent);
            Interlocked.Increment(ref _totalFailed);
            _logger.LogError("Failed to send email to {To}: {Message}", to, ex.Message);
            
            // Force reconnect on error
            if (client != null)
            {
                try { client.Disconnect(true); } catch { }
                try { client.Dispose(); } catch { }
                client = null;
            }
            
            return false;
        }
        finally
        {
            ReturnClientToPool(client);
        }
    }

    /// <summary>
    /// Send batch of emails with parallel processing and rate limiting
    /// Returns number of successfully sent emails
    /// </summary>
    public async Task<int> SendBatchAsync(IEnumerable<EmailMessage> messages, CancellationToken cancellationToken = default)
    {
        var messageList = messages.ToList();
        if (!messageList.Any()) return 0;

        _logger.LogInformation("Sending batch of {Count} emails", messageList.Count);
        
        var successCount = 0;
        var channel = Channel.CreateBounded<EmailMessage>(new BoundedChannelOptions(_maxConcurrentSends * 2)
        {
            FullMode = BoundedChannelFullMode.Wait
        });

        // Producer task - writes messages to channel
        var producerTask = Task.Run(async () =>
        {
            foreach (var msg in messageList)
            {
                if (cancellationToken.IsCancellationRequested) break;
                await channel.Writer.WriteAsync(msg, cancellationToken);
            }
            channel.Writer.Complete();
        }, cancellationToken);

        // Consumer tasks - reads from channel and sends emails
        var consumerTasks = Enumerable.Range(0, _maxConcurrentSends).Select(async _ =>
        {
            var localSuccess = 0;
            await foreach (var msg in channel.Reader.ReadAllAsync(cancellationToken))
            {
                try
                {
                    var result = await SendEmailAsync(msg.To, msg.Subject, msg.Body, msg.IsHtml);
                    if (result) localSuccess++;
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Failed to send email {Id}", msg.Id);
                }
            }
            return localSuccess;
        }).ToList();

        await producerTask;
        var results = await Task.WhenAll(consumerTasks);
        successCount = results.Sum();
        
        _logger.LogInformation("Batch complete: {SuccessCount}/{TotalCount} successful", successCount, messageList.Count);
        return successCount;
    }

    private MimeMessage CreateMimeMessage(string to, string subject, string body, bool isHtml)
    {
        var message = new MimeMessage();
        message.From.Add(new MailboxAddress(_senderName, _senderEmail));
        message.To.Add(MailboxAddress.Parse(to));
        message.Subject = subject;

        var bodyBuilder = new BodyBuilder();
        if (isHtml)
        {
            bodyBuilder.HtmlBody = body;
        }
        else
        {
            bodyBuilder.TextBody = body;
        }
        message.Body = bodyBuilder.ToMessageBody();

        return message;
    }

    public EmailMetrics GetMetrics()
    {
        var emailsLastMinute = 0;
        var oneMinuteAgo = DateTime.UtcNow.AddMinutes(-1);
        foreach (var timestamp in _sentTimestamps)
        {
            if (timestamp >= oneMinuteAgo) emailsLastMinute++;
        }

        return new EmailMetrics
        {
            TotalSent = Interlocked.Read(ref _totalSent),
            TotalSuccess = Interlocked.Read(ref _totalSuccess),
            TotalFailed = Interlocked.Read(ref _totalFailed),
            TotalRetried = Interlocked.Read(ref _totalRetried),
            AverageLatencyMs = _totalSent > 0 ? (double)Interlocked.Read(ref _totalLatencyMs) / _totalSent : 0,
            EmailsLastMinute = emailsLastMinute,
            LastSentAt = _lastSentAt
        };
    }

    public void Dispose()
    {
        if (_disposed) return;
        _disposed = true;

        _logger.LogInformation("Disposing email service...");

        while (_connectionPool.TryTake(out var client))
        {
            try
            {
                if (client.IsConnected)
                {
                    client.Disconnect(true);
                }
                client.Dispose();
            }
            catch { }
        }

        _poolSemaphore.Dispose();
        _rateLimitSemaphore.Dispose();
        _logger.LogInformation("Email service disposed. Final metrics: Sent={Sent}, Success={Success}, Failed={Failed}",
            _totalSent, _totalSuccess, _totalFailed);
    }
}
