using System.Collections.Concurrent;
using System.Reflection;
using foodmarket.Application.Common.Email;

namespace foodmarket.Api.Infrastructure.Email;

/// <summary>Загружает HTML-шаблоны из embedded resources
/// (`Resources/EmailTemplates/*.html`) и рендерит их подстановкой словаря.
/// Шаблоны кешируются после первого чтения — files embed'нуты в сборку,
/// перечитывать незачем.
///
/// Зачем embedded, а не файлы на диске: на проде нет gauantee, что
/// `ContentRootPath/Resources/EmailTemplates/` доедет до контейнера —
/// docker может монтировать только то, что ему нужно. Embedded — один
/// .dll, нет I/O при рендере, нет misconfiguration на staging.</summary>
public class EmailTemplates
{
    private static readonly ConcurrentDictionary<string, string> _cache = new();
    private static readonly Assembly _asm = typeof(EmailTemplates).Assembly;

    /// <summary>Базовое имя шаблона (без .html): "invite", "weekly-summary",
    /// "low-stock". Кидает FileNotFoundException если ресурс отсутствует —
    /// это явный bug в сборке, не runtime-условие.</summary>
    public (string Subject, string HtmlBody, string TextBody) Render(
        string name, IReadOnlyDictionary<string, string?> values)
    {
        var raw = LoadRaw(name);
        // Первая строка файла — Subject: <тема>. Остальное — HTML body. Plain text
        // получаем грубым strip-тагов из HTML — для типовых шаблонов выглядит ок.
        var lines = raw.Split('\n', 2);
        if (lines.Length < 2 || !lines[0].StartsWith("Subject:", StringComparison.Ordinal))
            throw new InvalidOperationException(
                $"Шаблон {name} должен начинаться с 'Subject: <тема>' первой строкой.");
        var subjectTemplate = lines[0]["Subject:".Length..].Trim();
        var htmlTemplate = lines[1];

        var subject = EmailTemplateRenderer.Render(subjectTemplate, values);
        var html = EmailTemplateRenderer.Render(htmlTemplate, values);
        var text = StripHtml(html);
        return (subject, html, text);
    }

    private static string LoadRaw(string name)
    {
        return _cache.GetOrAdd(name, key =>
        {
            // Имя ресурса формируется через AssemblyName.<folder>.<file> с подстановкой
            // path separator → '.'. У нас embedded под "foodmarket.Api.Resources.EmailTemplates.{key}.html".
            var resource = $"foodmarket.Api.Resources.EmailTemplates.{key}.html";
            using var stream = _asm.GetManifestResourceStream(resource)
                ?? throw new FileNotFoundException(
                    $"Email-шаблон {key}.html не найден в embedded resources. " +
                    $"Доступно: {string.Join(", ", _asm.GetManifestResourceNames())}");
            using var reader = new StreamReader(stream);
            return reader.ReadToEnd();
        });
    }

    private static string StripHtml(string html)
    {
        // Грубый strip для plain-fallback'а. Заменяет <br>/</p> на \n, удаляет остальные теги,
        // декодирует основные entities. Не для произвольного HTML, но для наших шаблонов хватает.
        var s = System.Text.RegularExpressions.Regex.Replace(html, @"<\s*br\s*/?\s*>", "\n",
            System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        s = System.Text.RegularExpressions.Regex.Replace(s, @"</\s*(p|h\d|div|li|tr)\s*>", "\n",
            System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        s = System.Text.RegularExpressions.Regex.Replace(s, @"<[^>]+>", "");
        s = s.Replace("&nbsp;", " ").Replace("&amp;", "&")
             .Replace("&lt;", "<").Replace("&gt;", ">")
             .Replace("&quot;", "\"").Replace("&#39;", "'");
        // Свернуть множественные пустые строки.
        s = System.Text.RegularExpressions.Regex.Replace(s, @"\n{3,}", "\n\n");
        return s.Trim();
    }
}
