using CvMatcher.Models.Responses;
using CvSearch.Data.Entities;
using Email.Data.Services;
using Email.Models.Clients;
using Email.Models.Requests;
using Microsoft.Extensions.Logging;

namespace CvSearchJob.Services;

/// <summary>
/// Sends job search results emails to the session user and the operator copy address,
/// with an optional CV PDF attachment.
/// </summary>
public sealed class CvSearchEmailSender
{
    private readonly IEmailApiClient _emailApi;
    private readonly IEmailTemplateService _emailTemplates;
    private readonly ILogger<CvSearchEmailSender> _logger;

    public CvSearchEmailSender(
        IEmailApiClient emailApi,
        IEmailTemplateService emailTemplates,
        ILogger<CvSearchEmailSender> logger)
    {
        _emailApi = emailApi;
        _emailTemplates = emailTemplates;
        _logger = logger;
    }

    /// <summary>
    /// Builds and sends the job search results email.
    /// Resolves the recipient list from <paramref name="toEmail"/> and the operator copy address
    /// stored in the email template. Does nothing when no recipients can be resolved.
    /// </summary>
    /// <param name="toEmail">Primary recipient (the user who triggered the search).</param>
    /// <param name="attachmentFileName">Relative filename of the CV PDF to attach, or <c>null</c>.</param>
    /// <param name="results">Ranked list of job search results to include in the email body.</param>
    /// <param name="keywords">CV keywords used to drive the job search.</param>
    /// <param name="providerNames">Names of the providers that were scanned.</param>
    /// <param name="language">Two-letter language code for template rendering.</param>
    /// <param name="ct">Cancellation token.</param>
    public async Task SendResultsAsync(
        string toEmail,
        string? attachmentFileName,
        IReadOnlyList<JobSearchResultEntity> results,
        IReadOnlyList<string> keywords,
        IReadOnlyList<string> providerNames,
        string language,
        string? location,
        CancellationToken ct)
    {
        var operatorCopy = _emailTemplates.GetOperatorCopy("email.search-results.subject", language);

        var recipients = new List<string>();
        if (!string.IsNullOrWhiteSpace(toEmail)) recipients.Add(toEmail);
        if (!string.IsNullOrWhiteSpace(operatorCopy) &&
            !recipients.Any(r => string.Equals(r, operatorCopy, StringComparison.OrdinalIgnoreCase)))
            recipients.Add(operatorCopy);

        if (recipients.Count == 0) return;

        var htmlBody = BuildBody(results, keywords, providerNames, language, location);
        var subject = _emailTemplates.Render("email.search-results.subject", language,
            ("count", results.Count.ToString()));

        try
        {
            await _emailApi.SendAsync(new SendEmailRequest
            {
                To = recipients,
                Subject = subject,
                HtmlBody = htmlBody,
                AttachmentPath = attachmentFileName
            }, ct);

            _logger.LogInformation("Job search results email sent to {Recipients}",
                string.Join(", ", recipients));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to send job search results email to {Recipients}",
                string.Join(", ", recipients));
        }
    }

    /// <summary>
    /// Renders the HTML email body from the results list.
    /// Returns the empty-results template when no results are present.
    /// Prepends a scan summary block showing the keywords and providers used.
    /// </summary>
    private string BuildBody(IReadOnlyList<JobSearchResultEntity> results, IReadOnlyList<string> keywords, IReadOnlyList<string> providerNames, string language, string? location)
    {
        var scanSummary = BuildScanSummary(keywords, providerNames, language, location);

        if (results.Count == 0)
            return scanSummary + _emailTemplates.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 summary = TryParseResult(r.ResultJson)?.Summary;
            var summaryHtml = string.IsNullOrWhiteSpace(summary)
                ? ""
                : $"<p style=\"margin:8px 0 0;color:#495057;font-size:14px;line-height:1.5;\">{summary}</p>";

            items.Append(_emailTemplates.Render("email.search-results.item", language,
                ("index",        (i + 1).ToString()),
                ("jobTitle",     r.JobTitle),
                ("score",        r.Score.ToString()),
                ("providerName", r.ProviderName),
                ("jobUrl",       r.JobUrl),
                ("summary",      summaryHtml)));
        }

        return _emailTemplates.Render("email.search-results.body", language,
            ("count", results.Count.ToString()),
            ("items", scanSummary + items.ToString()));
    }

    /// <summary>
    /// Renders the scan summary block via template, passing keyword tags and provider list as data.
    /// Keyword tags are built here because they are variable-count inline elements, not structural HTML.
    /// </summary>
    private string BuildScanSummary(IReadOnlyList<string> keywords, IReadOnlyList<string> providerNames, string language, string? location)
    {
        var keywordsHtml = keywords.Count > 0
            ? string.Join(" ", keywords.Select(k =>
                $"<span style=\"display:inline-block;background:#e9ecef;padding:2px 8px;margin:2px 2px 2px 0;font-size:12px;\">{k}</span>"))
            : $"<span style=\"color:#6c757d;font-size:12px;font-style:italic;\">{_emailTemplates.Get("email.search-results.keywords-empty", language)}</span>";

        var providers = providerNames.Count > 0
            ? string.Join(", ", providerNames)
            : _emailTemplates.Get("email.search-results.providers-empty", language);

        var locationDisplay = string.IsNullOrWhiteSpace(location)
            ? _emailTemplates.Get("email.search-results.location-empty", language)
            : location;

        return _emailTemplates.Render("email.search-results.scan-summary", language,
            ("keywordsHtml", keywordsHtml),
            ("providers",    providers),
            ("location",     locationDisplay));
    }

    /// <summary>
    /// Attempts to deserialise the stored result JSON into a <see cref="JobMatchResponse"/>.
    /// Returns <c>null</c> on parse failure so the email still renders without a summary.
    /// </summary>
    private static JobMatchResponse? TryParseResult(string json)
    {
        try
        {
            return System.Text.Json.JsonSerializer.Deserialize<JobMatchResponse>(json,
                new System.Text.Json.JsonSerializerOptions(System.Text.Json.JsonSerializerDefaults.Web));
        }
        catch { return null; }
    }
}
