using foodmarket.Api.Infrastructure.Email;
using foodmarket.Api.Infrastructure.Tenancy;
using foodmarket.Application.Common.Email;
using foodmarket.Domain.Sales;
using foodmarket.Infrastructure.Identity;
using foodmarket.Infrastructure.Persistence;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

namespace foodmarket.Api.Background;

/// <summary>Hangfire-джобы рассылки писем: weekly-summary (понедельник 07:00)
/// и low-stock (ежедневно 08:00). Бегут per-org через IgnoreQueryFilters +
/// AsyncLocal-tenant override для каждой обрабатываемой организации
/// (см. HttpContextTenantContext.UseOverride).
///
/// Каждая ошибка отправки логируется, но не валит весь джоб — продолжаем по
/// следующим оргам. Идемпотентность по неделе/дню — не реализована: повтор
/// джоба тем же утром обогатит почту админам, что приемлемо для MVP.</summary>
public class EmailNotificationJobs
{
    private readonly AppDbContext _db;
    private readonly IEmailSender _email;
    private readonly EmailTemplates _templates;
    private readonly UserManager<User> _users;
    private readonly IConfiguration _cfg;
    private readonly ILogger<EmailNotificationJobs> _log;

    public EmailNotificationJobs(AppDbContext db, IEmailSender email, EmailTemplates templates,
        UserManager<User> users, IConfiguration cfg, ILogger<EmailNotificationJobs> log)
    {
        _db = db; _email = email; _templates = templates; _users = users; _cfg = cfg; _log = log;
    }

    /// <summary>Weekly summary: владельцу каждой орги — выручка / транзакции /
    /// топ-5 товаров за последние 7 дней. Cron в HangfireJobsConfigurator —
    /// "0 7 * * 1" (понедельник 07:00 UTC).</summary>
    public async Task SendWeeklySummariesAsync(CancellationToken ct = default)
    {
        // `from` — это LINQ-ключевое слово, в query-синтаксе ломает парсер
        // даже как имя переменной (CS1525); используем fromDate/toDate.
        var toDate = DateTime.UtcNow;
        var fromDate = toDate.AddDays(-7);
        var orgs = await _db.Organizations.IgnoreQueryFilters()
            .Where(o => !o.IsArchived)
            .Select(o => new { o.Id, o.Name })
            .ToListAsync(ct);

        foreach (var org in orgs)
        {
            try
            {
                using var scope = HttpContextTenantContext.UseOverride(org.Id, isSuperAdmin: false);
                var revenueRaw = await _db.RetailSales
                    .Where(s => s.Status == RetailSaleStatus.Posted && s.Date >= fromDate && s.Date <= toDate)
                    .GroupBy(_ => 1)
                    .Select(g => new { Sum = g.Sum(s => s.IsReturn ? -s.Total : s.Total), Count = g.Count() })
                    .FirstOrDefaultAsync(ct);
                var revenue = revenueRaw?.Sum ?? 0m;
                var tx = revenueRaw?.Count ?? 0;
                var avgTicket = tx == 0 ? 0m : decimal.Round(revenue / tx, 2);

                // Топ-5 по выручке.
                var topRaw = await (from l in _db.RetailSaleLines.AsNoTracking()
                                    join s in _db.RetailSales.AsNoTracking() on l.RetailSaleId equals s.Id
                                    join p in _db.Products.AsNoTracking() on l.ProductId equals p.Id
                                    where s.Status == RetailSaleStatus.Posted
                                          && s.Date >= fromDate && s.Date <= toDate
                                    select new
                                    {
                                        p.Name,
                                        Revenue = s.IsReturn ? -l.LineTotal : l.LineTotal,
                                        Qty = s.IsReturn ? -l.Quantity : l.Quantity,
                                    }).ToListAsync(ct);
                var top = topRaw.GroupBy(x => x.Name)
                    .Select(g => new { Name = g.Key, Revenue = g.Sum(x => x.Revenue), Qty = g.Sum(x => x.Qty) })
                    .OrderByDescending(x => x.Revenue).Take(5).ToList();
                var topHtml = top.Count == 0 ? "" :
                    "<table style=\"border-collapse:collapse;width:100%;\"><tr><th align=\"left\">Товар</th><th align=\"right\">Кол-во</th><th align=\"right\">Выручка</th></tr>"
                    + string.Join("", top.Select(t =>
                        $"<tr><td>{System.Net.WebUtility.HtmlEncode(t.Name)}</td><td align=\"right\">{t.Qty:0.##}</td><td align=\"right\">{t.Revenue:0.##}</td></tr>"))
                    + "</table>";

                var recipients = await GetOwnerEmailsAsync(org.Id, ct);
                if (recipients.Count == 0) continue;

                var (subject, html, text) = _templates.Render("weekly-summary", new Dictionary<string, string?>
                {
                    ["organizationName"] = org.Name,
                    ["periodFrom"] = fromDate.ToString("yyyy-MM-dd"),
                    ["periodTo"] = toDate.ToString("yyyy-MM-dd"),
                    ["revenue"] = revenue.ToString("0.##"),
                    ["transactions"] = tx.ToString(),
                    ["avgTicket"] = avgTicket.ToString("0.##"),
                    ["topProductsHtml"] = topHtml,
                    ["reportsUrl"] = $"{_cfg["App:PublicUrl"]}/reports/sales",
                });

                foreach (var to_ in recipients)
                    await _email.SendHtmlAsync(to_, subject, html, text, ct);
                _log.LogInformation("Weekly summary отправлен для {Org} → {Count} получателей",
                    org.Name, recipients.Count);
            }
            catch (Exception ex)
            {
                _log.LogWarning(ex, "Weekly summary upset для org {OrgId}", org.Id);
            }
        }
    }

    /// <summary>Low-stock: владельцу каждой орги — список товаров,
    /// у которых quantity &lt; MinStock. Cron — "0 8 * * *" (ежедневно 08:00 UTC).</summary>
    public async Task SendLowStockAlertsAsync(CancellationToken ct = default)
    {
        var orgs = await _db.Organizations.IgnoreQueryFilters()
            .Where(o => !o.IsArchived).Select(o => new { o.Id, o.Name }).ToListAsync(ct);

        foreach (var org in orgs)
        {
            try
            {
                using var scope = HttpContextTenantContext.UseOverride(org.Id, isSuperAdmin: false);
                // Считаем по сумме остатков на всех складах vs MinStock.
                var lowItems = await (from p in _db.Products.AsNoTracking()
                                      where p.MinStock != null
                                      let stockSum = _db.Stocks.Where(s => s.ProductId == p.Id).Sum(s => (decimal?)s.Quantity) ?? 0m
                                      where stockSum < p.MinStock
                                      orderby (decimal)(p.MinStock! - stockSum) descending
                                      select new { p.Name, p.Article, MinStock = p.MinStock!.Value, Stock = stockSum })
                    .Take(50).ToListAsync(ct);

                if (lowItems.Count == 0) continue;

                var recipients = await GetOwnerEmailsAsync(org.Id, ct);
                if (recipients.Count == 0) continue;

                var itemsHtml = "<table style=\"border-collapse:collapse;width:100%;\"><tr><th align=\"left\">Товар</th><th align=\"left\">Артикул</th><th align=\"right\">Остаток</th><th align=\"right\">Минимум</th></tr>"
                    + string.Join("", lowItems.Select(i =>
                        $"<tr><td>{System.Net.WebUtility.HtmlEncode(i.Name)}</td><td>{System.Net.WebUtility.HtmlEncode(i.Article ?? "—")}</td><td align=\"right\" style=\"color:#b91c1c;\">{i.Stock:0.##}</td><td align=\"right\">{i.MinStock:0.##}</td></tr>"))
                    + "</table>";

                var (subject, html, text) = _templates.Render("low-stock", new Dictionary<string, string?>
                {
                    ["organizationName"] = org.Name,
                    ["productCount"] = lowItems.Count.ToString(),
                    ["itemsHtml"] = itemsHtml,
                    ["stockUrl"] = $"{_cfg["App:PublicUrl"]}/inventory/stock",
                });
                foreach (var to_ in recipients)
                    await _email.SendHtmlAsync(to_, subject, html, text, ct);
                _log.LogInformation("Low-stock alert для {Org}: {Count} товаров → {Recipients} получателей",
                    org.Name, lowItems.Count, recipients.Count);
            }
            catch (Exception ex)
            {
                _log.LogWarning(ex, "Low-stock upset для org {OrgId}", org.Id);
            }
        }
    }

    private async Task<List<string>> GetOwnerEmailsAsync(Guid orgId, CancellationToken ct)
    {
        // Получатели — активные User'ы орги с ролью Admin (через Identity-маппинг).
        // Не Employee, а User: у Employee email — контактный, у User — для входа.
        var adminUserIds = await _db.UserRoles.AsNoTracking()
            .Join(_db.Roles.AsNoTracking(), ur => ur.RoleId, r => r.Id, (ur, r) => new { ur.UserId, r.Name })
            .Where(x => x.Name == "Admin")
            .Select(x => x.UserId)
            .ToListAsync(ct);
        return await _db.Users.IgnoreQueryFilters()
            .Where(u => adminUserIds.Contains(u.Id) && u.OrganizationId == orgId
                     && u.IsActive && u.Email != null)
            .Select(u => u.Email!)
            .ToListAsync(ct);
    }
}
