using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using PowderCoating.Application.Interfaces;
using SendGrid;
using SendGrid.Helpers.Mail;

namespace PowderCoating.Infrastructure.Services;

public class EmailService : IEmailService
{
    private readonly IConfiguration _configuration;
    private readonly ILogger<EmailService> _logger;
    private readonly IHostEnvironment _hostEnvironment;

    public EmailService(IConfiguration configuration, ILogger<EmailService> logger, IHostEnvironment hostEnvironment)
    {
        _configuration = configuration;
        _logger = logger;
        _hostEnvironment = hostEnvironment;
    }

    /// <summary>
    /// Sends an email via SendGrid. In non-production environments the subject is prefixed with
    /// "[ENV]" (e.g. "[Development]", "[Staging]") so recipients immediately know the email came
    /// from a test environment and don't act on it as if it were real.
    /// </summary>
    public async Task<(bool Success, string? ErrorMessage)> SendEmailAsync(
        string toEmail,
        string toName,
        string subject,
        string plainTextBody,
        string? htmlBody = null,
        byte[]? attachmentData = null,
        string? attachmentFilename = null,
        string? attachmentContentType = null,
        string? replyToEmail = null,
        string? replyToName = null)
    {
        var apiKey = _configuration["SendGrid:ApiKey"];
        if (string.IsNullOrWhiteSpace(apiKey) || apiKey.StartsWith("your-") || apiKey == "SG.placeholder")
        {
            _logger.LogWarning("SendGrid API key is not configured. Email to {ToEmail} skipped.", toEmail);
            return (false, "SendGrid not configured");
        }

        if (!_hostEnvironment.IsProduction())
            subject = $"[{_hostEnvironment.EnvironmentName}] {subject}";

        (toEmail, toName) = RedirectIfNonProd(toEmail, toName);

        var fromEmail = _configuration["SendGrid:FromEmail"] ?? "noreply@example.com";
        var fromName  = _configuration["SendGrid:FromName"]  ?? "Powder Coating";

        try
        {
            var client = new SendGridClient(apiKey);
            var msg = MailHelper.CreateSingleEmail(
                new EmailAddress(fromEmail, fromName),
                new EmailAddress(toEmail, toName),
                subject, plainTextBody, htmlBody ?? plainTextBody);

            if (!string.IsNullOrWhiteSpace(replyToEmail))
                msg.SetReplyTo(new EmailAddress(replyToEmail, replyToName));

            if (attachmentData != null && attachmentData.Length > 0 && !string.IsNullOrWhiteSpace(attachmentFilename))
            {
                await msg.AddAttachmentAsync(
                    attachmentFilename,
                    new MemoryStream(attachmentData),
                    attachmentContentType ?? "application/octet-stream");
            }

            return await DispatchAsync(client, msg, toEmail, subject);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Exception sending email to {ToEmail}", toEmail);
            return (false, ex.Message);
        }
    }

    /// <summary>
    /// Sends an email with one or more file attachments via SendGrid. Intended for scenarios
    /// such as attaching job photos to a ready-for-pickup notification. Callers are responsible
    /// for keeping total attachment size under SendGrid's 30 MB per-message limit; this method
    /// skips any attachment whose Data array is empty.
    /// </summary>
    public async Task<(bool Success, string? ErrorMessage)> SendEmailWithAttachmentsAsync(
        string toEmail,
        string toName,
        string subject,
        string plainTextBody,
        string? htmlBody = null,
        IList<EmailAttachment>? attachments = null,
        string? replyToEmail = null,
        string? replyToName = null)
    {
        var apiKey = _configuration["SendGrid:ApiKey"];
        if (string.IsNullOrWhiteSpace(apiKey) || apiKey.StartsWith("your-") || apiKey == "SG.placeholder")
        {
            _logger.LogWarning("SendGrid API key is not configured. Email to {ToEmail} skipped.", toEmail);
            return (false, "SendGrid not configured");
        }

        if (!_hostEnvironment.IsProduction())
            subject = $"[{_hostEnvironment.EnvironmentName}] {subject}";

        (toEmail, toName) = RedirectIfNonProd(toEmail, toName);

        var fromEmail = _configuration["SendGrid:FromEmail"] ?? "noreply@example.com";
        var fromName  = _configuration["SendGrid:FromName"]  ?? "Powder Coating";

        try
        {
            var client = new SendGridClient(apiKey);
            var msg = MailHelper.CreateSingleEmail(
                new EmailAddress(fromEmail, fromName),
                new EmailAddress(toEmail, toName),
                subject, plainTextBody, htmlBody ?? plainTextBody);

            if (!string.IsNullOrWhiteSpace(replyToEmail))
                msg.SetReplyTo(new EmailAddress(replyToEmail, replyToName));

            if (attachments != null)
            {
                foreach (var attachment in attachments.Where(a => a.Data.Length > 0))
                {
                    await msg.AddAttachmentAsync(
                        attachment.Filename,
                        new MemoryStream(attachment.Data),
                        attachment.ContentType);
                }
            }

            return await DispatchAsync(client, msg, toEmail, subject);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Exception sending email with attachments to {ToEmail}", toEmail);
            return (false, ex.Message);
        }
    }

    /// <summary>
    /// In non-production environments, redirects outbound email to <c>SendGrid:DevRedirectEmail</c>
    /// so real customers are never contacted outside of production. Double-gated on environment
    /// name AND the config value so a misconfigured prod deploy can't accidentally redirect.
    /// </summary>
    private (string email, string name) RedirectIfNonProd(string toEmail, string toName)
    {
        if (_hostEnvironment.IsProduction()) return (toEmail, toName);
        var devEmail = _configuration["SendGrid:DevRedirectEmail"];
        if (string.IsNullOrWhiteSpace(devEmail)) return (toEmail, toName);
        _logger.LogWarning("Non-production environment: redirecting email from {Original} to dev address {Dev}", toEmail, devEmail);
        return (devEmail, $"[DEV → {toName} <{toEmail}>]");
    }

    /// <summary>
    /// Sends the built SendGrid message and interprets the HTTP response. Extracted so both
    /// send methods share identical dispatch and logging logic.
    /// </summary>
    private async Task<(bool Success, string? ErrorMessage)> DispatchAsync(
        SendGridClient client, SendGridMessage msg, string toEmail, string subject)
    {
        var response = await client.SendEmailAsync(msg);

        if ((int)response.StatusCode >= 200 && (int)response.StatusCode < 300)
        {
            _logger.LogInformation("Email sent to {ToEmail}: {Subject}", toEmail, subject);
            return (true, null);
        }

        var body = await response.Body.ReadAsStringAsync();
        _logger.LogWarning("SendGrid returned {StatusCode} for {ToEmail}: {Body}", response.StatusCode, toEmail, body);
        return (false, $"HTTP {(int)response.StatusCode}");
    }
}
