using System.Net;
using foodmarket.Api.Integrations.Telegram;
using foodmarket.Domain.Inventory;
using foodmarket.Domain.Sales;
using foodmarket.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;

namespace foodmarket.Api.Background;

/// <summary>Раз в день в 09:00 по МСК шлёт владельцу каждой org Telegram-сводку:
/// выручка вчера, количество чеков, топ-3 товаров (qty*price), low-stock-список.
///
/// Регистрация — в HangfireJobsConfigurator (cron <c>0 6 * * *</c>; в UTC это
/// 09:00 МСК, потому что MSK = UTC+3).
///
/// На org без привязанного OwnerTelegramChatId или с null AccountOwnerUserId
/// пропускаем без логов (нет адресата).</summary>
public sealed class OwnerDailySummaryJob
{
    private readonly AppDbContext _db;
    private readonly ITelegramBotClient _tg;
    private readonly ILogger<OwnerDailySummaryJob> _log;

    public OwnerDailySummaryJob(AppDbContext db, ITelegramBotClient tg, ILogger<OwnerDailySummaryJob> log)
    {
        _db = db;
        _tg = tg;
        _log = log;
    }

    /// <summary>Hangfire-entry. Пробегает по всем org с OwnerTelegramChatId !=
    /// null, рендерит сводку и шлёт. Best-effort на каждой org — одна неудача
    /// не валит остальных.</summary>
    public async Task RunAsync(CancellationToken ct)
    {
        if (!_tg.IsEnabled)
        {
            _log.LogInformation("Telegram bot disabled (no token) — skipping daily summary");
            return;
        }
        var orgs = await _db.Organizations.IgnoreQueryFilters()
            .Where(o => o.OwnerTelegramChatId != null && !o.IsArchived && o.IsActive)
            .Select(o => new { o.Id, o.Name, ChatId = o.OwnerTelegramChatId!.Value })
            .ToListAsync(ct);
        foreach (var o in orgs)
        {
            try
            {
                var text = await RenderSummaryAsync(o.Id, o.Name, ct);
                await _tg.SendMessageAsync(o.ChatId, text, ct);
            }
            catch (Exception ex)
            {
                _log.LogWarning(ex, "Daily summary failed for org={OrgId}", o.Id);
            }
        }
    }

    /// <summary>Чистый рендерер — exposед публично для тестов. Берёт «вчерашний»
    /// день в UTC (UTC простоты ради; для МСК-таймзоны 09:00 рассылка → вчера
    /// UTC примерно совпадает со вчера МСК с поправкой 3ч, для бизнес-сводки
    /// этого достаточно).</summary>
    public async Task<string> RenderSummaryAsync(Guid orgId, string orgName, CancellationToken ct)
    {
        var todayStart = DateTime.UtcNow.Date;
        var yStart = todayStart.AddDays(-1);
        var yEnd = todayStart;

        // Розничные продажи вчера, проведённые.
        var ySales = _db.RetailSales.IgnoreQueryFilters()
            .Where(s => s.OrganizationId == orgId
                && s.Status == RetailSaleStatus.Posted
                && !s.IsReturn
                && s.Date >= yStart && s.Date < yEnd);
        var revenue = await ySales.SumAsync(s => (decimal?)s.Total, ct) ?? 0m;
        var count = await ySales.CountAsync(ct);

        // Топ-3 товара по выручке вчера.
        var top3 = await (from sl in _db.Set<RetailSaleLine>().IgnoreQueryFilters()
                          join s in _db.RetailSales.IgnoreQueryFilters() on sl.RetailSaleId equals s.Id
                          join p in _db.Products.IgnoreQueryFilters() on sl.ProductId equals p.Id
                          where s.OrganizationId == orgId
                              && s.Status == RetailSaleStatus.Posted
                              && !s.IsReturn
                              && s.Date >= yStart && s.Date < yEnd
                          group new { sl, p } by new { sl.ProductId, p.Name } into g
                          orderby g.Sum(x => x.sl.LineTotal) descending
                          select new
                          {
                              g.Key.Name,
                              Total = g.Sum(x => x.sl.LineTotal),
                              Qty = g.Sum(x => x.sl.Quantity),
                          })
            .Take(3).ToListAsync(ct);

        // Low-stock: продукты у которых остаток на ЛЮБОМ складе ниже MinStock.
        var low = await (from p in _db.Products.IgnoreQueryFilters()
                         join s in _db.Stocks.IgnoreQueryFilters() on p.Id equals s.ProductId
                         join st in _db.Stores.IgnoreQueryFilters() on s.StoreId equals st.Id
                         where p.OrganizationId == orgId
                             && p.MinStock != null
                             && s.Quantity < p.MinStock
                         orderby s.Quantity ascending
                         select new { p.Name, StoreName = st.Name, s.Quantity, p.MinStock })
            .Take(5).ToListAsync(ct);

        // HTML-кодирование чтобы спецсимволы в названиях не сломали parse_mode=HTML.
        string Esc(string s) => WebUtility.HtmlEncode(s);
        string Money(decimal d) => $"{d:N0} ₸";

        var sb = new System.Text.StringBuilder();
        sb.Append($"<b>Food Market — сводка за {yStart:dd.MM}</b>\n");
        sb.Append($"<i>{Esc(orgName)}</i>\n\n");
        sb.Append($"💰 Выручка: <b>{Money(revenue)}</b>\n");
        sb.Append($"🧾 Чеков: <b>{count}</b>\n");
        if (count > 0)
        {
            sb.Append($"📊 Средний чек: <b>{Money(revenue / count)}</b>\n");
        }
        if (top3.Count > 0)
        {
            sb.Append("\n<b>Топ-3 товара</b>\n");
            for (int i = 0; i < top3.Count; i++)
            {
                sb.Append($"{i + 1}. {Esc(top3[i].Name)} — {Money(top3[i].Total)} ({top3[i].Qty:N0} шт.)\n");
            }
        }
        if (low.Count > 0)
        {
            sb.Append("\n⚠️ <b>Низкие остатки</b>\n");
            foreach (var l in low)
            {
                sb.Append($"• {Esc(l.Name)} ({Esc(l.StoreName)}) — {l.Quantity:N0} / мин {l.MinStock:N0}\n");
            }
        }
        return sb.ToString();
    }
}
