using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.EntityFrameworkCore;
using PowderCoating.Infrastructure.Data;

namespace PowderCoating.Web.Filters;

/// <summary>
/// Action filter that injects active, non-dismissed platform announcements into
/// <c>ViewBag.ActiveAnnouncements</c> before every authenticated page render.
/// The shared layout (<c>_Layout.cshtml</c>) reads this bag property to render
/// dismissible alert banners at the top of the page.
/// <para>
/// Design decisions:
/// <list type="bullet">
///   <item>
///     The filter queries <see cref="ApplicationDbContext"/> directly (rather than
///     via <c>IUnitOfWork</c>) because the announcement and dismissal tables are
///     platform-level — they do not go through the tenant-scoped unit of work.
///   </item>
///   <item>
///     Announcements are filtered in SQL (not in memory) to avoid loading the
///     full announcement set on every request.  The <c>StartsAt</c>/<c>ExpiresAt</c>
///     window and the <c>Target</c>/<c>TargetCompanyId</c>/<c>TargetPlan</c>
///     predicates are all pushed down to the database.
///   </item>
///   <item>
///     Dismissed announcement IDs are loaded in a second query (rather than a
///     JOIN) to keep the EF query straightforward and avoid a potential
///     Cartesian explosion when a user has many dismissals.
///   </item>
/// </list>
/// </para>
/// </summary>
public class AnnouncementFilter : IAsyncActionFilter
{
    private readonly ApplicationDbContext _db;
    private readonly IHttpContextAccessor _httpContextAccessor;

    /// <summary>
    /// Initialises the filter with the database context and HTTP context accessor.
    /// </summary>
    public AnnouncementFilter(ApplicationDbContext db, IHttpContextAccessor httpContextAccessor)
    {
        _db = db;
        _httpContextAccessor = httpContextAccessor;
    }

    /// <summary>
    /// Queries active announcements targeting the current user's company and
    /// subscription plan, removes any that the user has already dismissed, and
    /// stores the remainder in <c>ViewBag.ActiveAnnouncements</c> before
    /// invoking the next action delegate.
    /// <para>
    /// The filter is a no-op for unauthenticated users and for non-controller
    /// action contexts (e.g. page filters, view components) — the
    /// <c>context.Controller is Controller</c> guard ensures we only set
    /// ViewBag when we have an MVC controller that will render a view.
    /// </para>
    /// </summary>
    /// <param name="context">The action-executing context for the current request.</param>
    /// <param name="next">The delegate to invoke to execute the action.</param>
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // Only inject for authenticated users making page requests
        if (context.Controller is Controller controller &&
            context.HttpContext.User.Identity?.IsAuthenticated == true)
        {
            var userId = context.HttpContext.User.FindFirst(
                System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;

            var companyIdStr = context.HttpContext.User.FindFirst("CompanyId")?.Value;
            int.TryParse(companyIdStr, out var companyId);

            // Plan comes from middleware-injected Context.Items (set per-request from Company record)
            var plan = context.HttpContext.Items["SubscriptionPlan"] as int? ?? 0;

            var now = DateTime.UtcNow;

            // Load active announcements targeting this user's company/plan
            var announcements = await _db.Announcements
                .Where(a => a.IsActive
                    && a.StartsAt <= now
                    && (a.ExpiresAt == null || a.ExpiresAt > now)
                    && (a.Target == "All"
                        || (a.Target == "Plan" && a.TargetPlan == plan)
                        || (a.Target == "Company" && a.TargetCompanyId == companyId)))
                .ToListAsync();

            if (announcements.Count > 0 && userId != null)
            {
                // Filter out dismissed ones
                var dismissedIds = await _db.AnnouncementDismissals
                    .Where(d => d.UserId == userId && announcements.Select(a => a.Id).Contains(d.AnnouncementId))
                    .Select(d => d.AnnouncementId)
                    .ToListAsync();

                announcements = announcements.Where(a => !dismissedIds.Contains(a.Id)).ToList();
            }

            controller.ViewBag.ActiveAnnouncements = announcements;
        }

        await next();
    }
}
