using CvMatcher.Models.Responses; using CvSearch.Data.Entities; using MailKit.Net.Smtp; using MailKit.Security; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using MimeKit; using MyAi.Data.Services; namespace CvSearchJob.Services; public sealed class CvSearchEmailSender { private readonly IConfiguration _config; private readonly ITemplateService _templates; private readonly ILogger _logger; public CvSearchEmailSender(IConfiguration config, ITemplateService templates, ILogger logger) { _config = config; _templates = templates; _logger = logger; } public async Task SendResultsAsync( string toEmail, string? attachmentPath, IReadOnlyList results, string language, CancellationToken ct) { var smtpHost = _config["Smtp:Host"]; var smtpPort = int.TryParse(_config["Smtp:Port"], out var port) ? port : 587; var smtpUser = _config["Smtp:Username"]; var smtpPass = _config["Smtp:Password"]; var useStartTls = bool.TryParse(_config["Smtp:UseStartTls"], out var tls) && tls; var contactToEmail = _config["Contact:ToEmail"]; if (string.IsNullOrWhiteSpace(smtpHost)) return; var recipients = new List(); if (!string.IsNullOrWhiteSpace(toEmail)) recipients.Add(toEmail); if (!string.IsNullOrWhiteSpace(contactToEmail) && !recipients.Any(r => string.Equals(r, contactToEmail, StringComparison.OrdinalIgnoreCase))) recipients.Add(contactToEmail); if (recipients.Count == 0) return; var body = BuildBody(results, language); var subject = _templates.Render("email.search-results.subject", language, ("count", results.Count.ToString())); var environmentName = Environment.GetEnvironmentVariable("APP_ENVIRONMENT_NAME") ?? "Development"; foreach (var recipient in recipients) { var msg = new MimeMessage(); msg.From.Add(MailboxAddress.Parse(smtpUser!)); msg.To.Add(MailboxAddress.Parse(recipient)); msg.Subject = $"[{environmentName}] {subject}"; var builder = new BodyBuilder { TextBody = body }; if (!string.IsNullOrWhiteSpace(attachmentPath) && File.Exists(attachmentPath)) builder.Attachments.Add(attachmentPath); msg.Body = builder.ToMessageBody(); try { using var client = new SmtpClient(); var tls2 = useStartTls ? SecureSocketOptions.StartTls : SecureSocketOptions.Auto; await client.ConnectAsync(smtpHost, smtpPort, tls2, ct); if (!string.IsNullOrWhiteSpace(smtpUser)) await client.AuthenticateAsync(smtpUser, smtpPass ?? string.Empty, ct); await client.SendAsync(msg, ct); await client.DisconnectAsync(true, ct); _logger.LogInformation("Job search results email sent to {Recipient}", recipient); } catch (Exception ex) { _logger.LogError(ex, "Failed to send job search results email to {Recipient}", recipient); } } } private string BuildBody(IReadOnlyList results, string language) { if (results.Count == 0) return _templates.Get("email.search-results.empty", language); var items = new System.Text.StringBuilder(); for (int i = 0; i < results.Count; i++) { var r = results[i]; var matchResp = TryParseResult(r.ResultJson); items.AppendLine($"{i + 1}. {r.JobTitle} ({r.Score}% match) [{r.ProviderName}]"); items.AppendLine($" {r.JobUrl}"); if (matchResp is not null && !string.IsNullOrWhiteSpace(matchResp.Summary)) items.AppendLine($" {matchResp.Summary}"); items.AppendLine(); } return _templates.Render("email.search-results.body", language, ("count", results.Count.ToString()), ("items", items.ToString())); } private static JobMatchResponse? TryParseResult(string json) { try { return System.Text.Json.JsonSerializer.Deserialize(json, new System.Text.Json.JsonSerializerOptions(System.Text.Json.JsonSerializerDefaults.Web)); } catch { return null; } } }