using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using PowderCoating.Infrastructure.Data;
using PowderCoating.Shared.Constants;
using PowderCoating.Web.Services;
using System.Text;

namespace PowderCoating.Web.Controllers;

// Intentional exception: queries ASP.NET Identity ApplicationUser across all tenants with Include(u => u.Company); Identity entities live outside IUnitOfWork. See docs/DATA_ACCESS_ARCHITECTURE.md — Permanent Exceptions.
[Authorize(Policy = AppConstants.Policies.SuperAdminOnly)]
public class UserActivityController : Controller
{
    private readonly ApplicationDbContext _db;
    private readonly IOnlineUserTracker _tracker;

    public UserActivityController(ApplicationDbContext db, IOnlineUserTracker tracker)
    {
        _db = db;
        _tracker = tracker;
    }

    /// <summary>
    /// Renders a paginated, filterable table of all platform users across every tenant
    /// with their last-login date and account status (SuperAdmin only).
    /// <para>
    /// Filters are composable: company, role, active/inactive status, login-age bucket,
    /// and free-text search can all be combined. Sorting is handled via a switch
    /// expression; the default sort puts users who have never logged in first (NULL
    /// last-login dates sorted before oldest dates), then ascending by date, so the
    /// most at-risk accounts appear at the top by default.
    /// </para>
    /// <para>
    /// Platform-wide summary KPI stats (total users, active users, never logged in,
    /// inactive 30+ days) are computed from a <em>separate</em> un-filtered base query
    /// rather than the current filter query so the header cards always reflect
    /// platform totals regardless of which filter is active.
    /// </para>
    /// <para>
    /// All queries use <c>IgnoreQueryFilters()</c> so SuperAdmins see users from all
    /// companies, including those in inactive or deleted tenants.
    /// </para>
    /// </summary>
    public async Task<IActionResult> Index(
        int? companyId,
        string? role,
        string? activeStatus,
        string? loginAge,
        string? search,
        string sortCol = "LastLogin",
        string sortDir = "asc",
        int page = 1,
        int pageSize = 50)
    {
        pageSize = pageSize is 25 or 50 or 100 ? pageSize : 50;
        page = Math.Max(1, page);

        var query = _db.Users.AsNoTracking().IgnoreQueryFilters()
            .Include(u => u.Company)
            .AsQueryable();

        if (companyId.HasValue)
            query = query.Where(u => u.CompanyId == companyId);

        if (!string.IsNullOrWhiteSpace(role))
            query = query.Where(u => u.CompanyRole == role);

        if (activeStatus == "active")
            query = query.Where(u => u.IsActive);
        else if (activeStatus == "inactive")
            query = query.Where(u => !u.IsActive);

        if (!string.IsNullOrWhiteSpace(search))
            query = query.Where(u =>
                u.FirstName.Contains(search) ||
                u.LastName.Contains(search) ||
                u.Email!.Contains(search));

        var now = DateTime.UtcNow;
        if (loginAge == "never")
            query = query.Where(u => u.LastLoginDate == null);
        else if (loginAge == "90plus")
            query = query.Where(u => u.LastLoginDate == null || u.LastLoginDate < now.AddDays(-90));
        else if (loginAge == "30plus")
            query = query.Where(u => u.LastLoginDate == null || u.LastLoginDate < now.AddDays(-30));
        else if (loginAge == "7plus")
            query = query.Where(u => u.LastLoginDate == null || u.LastLoginDate < now.AddDays(-7));
        else if (loginAge == "recent")
            query = query.Where(u => u.LastLoginDate != null && u.LastLoginDate >= now.AddDays(-7));

        query = (sortCol, sortDir) switch
        {
            ("Company", "asc")    => query.OrderBy(u => u.Company!.CompanyName),
            ("Company", "desc")   => query.OrderByDescending(u => u.Company!.CompanyName),
            ("Name", "asc")       => query.OrderBy(u => u.LastName).ThenBy(u => u.FirstName),
            ("Name", "desc")      => query.OrderByDescending(u => u.LastName).ThenBy(u => u.FirstName),
            ("Role", "asc")       => query.OrderBy(u => u.CompanyRole),
            ("Role", "desc")      => query.OrderByDescending(u => u.CompanyRole),
            ("LastLogin", "desc") => query.OrderByDescending(u => u.LastLoginDate),
            ("Created", "asc")    => query.OrderBy(u => u.CreatedAt),
            ("Created", "desc")   => query.OrderByDescending(u => u.CreatedAt),
            _                     => query.OrderBy(u => u.LastLoginDate == null).ThenBy(u => u.LastLoginDate)
        };

        var totalCount = await query.CountAsync();

        var users = await query
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .Select(u => new UserActivityRow
            {
                Id = u.Id,
                CompanyId = u.CompanyId,
                CompanyName = u.Company != null ? u.Company.CompanyName : "—",
                FullName = u.FirstName + " " + u.LastName,
                Email = u.Email ?? string.Empty,
                CompanyRole = u.CompanyRole ?? "—",
                IsActive = u.IsActive,
                LastLoginDate = u.LastLoginDate,
                CreatedAt = u.CreatedAt,
                DaysSinceLogin = u.LastLoginDate.HasValue
                    ? (int)(now - u.LastLoginDate.Value).TotalDays
                    : (int?)null
            })
            .ToListAsync();

        // Summary stats — CountAsync queries instead of loading all users into memory
        var statsBase = _db.Users.AsNoTracking().IgnoreQueryFilters();
        var cutoff30 = now.AddDays(-30);
        ViewBag.TotalUsers    = await statsBase.CountAsync();
        ViewBag.ActiveUsers   = await statsBase.CountAsync(u => u.IsActive);
        ViewBag.NeverLoggedIn = await statsBase.CountAsync(u => u.LastLoginDate == null);
        ViewBag.Inactive30    = await statsBase.CountAsync(u => u.LastLoginDate == null || u.LastLoginDate < cutoff30);

        // Company list for filter dropdown
        ViewBag.Companies = await _db.Companies.AsNoTracking().IgnoreQueryFilters()
            .Where(c => !c.IsDeleted)
            .OrderBy(c => c.CompanyName)
            .Select(c => new { c.Id, c.CompanyName })
            .ToListAsync();

        ViewBag.CompanyIdFilter = companyId;
        ViewBag.RoleFilter = role;
        ViewBag.ActiveStatusFilter = activeStatus;
        ViewBag.LoginAgeFilter = loginAge;
        ViewBag.Search = search;
        ViewBag.SortCol = sortCol;
        ViewBag.SortDir = sortDir;
        ViewBag.Page = page;
        ViewBag.PageSize = pageSize;
        ViewBag.TotalCount = totalCount;
        ViewBag.TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize);

        return View(users);
    }

    /// <summary>
    /// Renders a live view of users who have been active within the last 15 minutes,
    /// sourced from the in-memory <see cref="IOnlineUserTracker"/> service.
    /// <para>
    /// The tracker only holds lightweight session data (userId, email, display name,
    /// current path, IP). Company name and role are enriched from the database in a
    /// single batch query keyed by user IDs, avoiding N+1 lookups. The window of 15
    /// minutes is hardcoded here (passed to the tracker) and echoed in ViewBag so the
    /// view can display "active in last 15 min" without embedding a magic number in
    /// the template.
    /// </para>
    /// </summary>
    public async Task<IActionResult> Online()
    {
        var active = _tracker.GetActiveUsers(windowMinutes: 15);

        // Enrich with company name + role from DB (batch query by user IDs)
        var userIds = active.Select(e => e.UserId).ToList();
        var dbUsers = await _db.Users.AsNoTracking().IgnoreQueryFilters()
            .Where(u => userIds.Contains(u.Id))
            .Include(u => u.Company)
            .Select(u => new
            {
                u.Id,
                u.CompanyRole,
                CompanyName = u.Company != null ? u.Company.CompanyName : null,
                u.CompanyId
            })
            .ToDictionaryAsync(u => u.Id);

        var rows = active.Select(e =>
        {
            dbUsers.TryGetValue(e.UserId, out var db);
            return new OnlineUserRow
            {
                UserId      = e.UserId,
                Email       = e.Email,
                DisplayName = e.DisplayName,
                IsSuperAdmin = e.IsSuperAdmin,
                CompanyName = db?.CompanyName ?? e.CompanyName ?? (e.IsSuperAdmin ? "Platform" : "—"),
                CompanyId   = db?.CompanyId ?? e.CompanyId,
                Role        = e.IsSuperAdmin ? "SuperAdmin" : (db?.CompanyRole ?? "—"),
                CurrentPath = e.CurrentPath,
                IpAddress   = e.IpAddress,
                LastSeen    = e.LastSeen
            };
        }).ToList();

        ViewBag.WindowMinutes = 15;
        return View(rows);
    }

    /// <summary>
    /// Exports the current user-activity query result as a UTF-8 CSV file for
    /// offline analysis in Excel or similar tools.
    /// <para>
    /// Applies the same filter logic as <see cref="Index"/> (minus sorting and
    /// pagination) and streams the full result set. Column values that contain
    /// commas, quotes, or newlines are wrapped in double-quotes via
    /// <see cref="CsvEscape"/> to produce a valid RFC 4180 CSV.
    /// The filename is date-stamped (<c>user-activity-yyyyMMdd.csv</c>) so multiple
    /// exports on different days do not overwrite each other when saved to a folder.
    /// </para>
    /// </summary>
    public async Task<IActionResult> ExportCsv(
        int? companyId, string? role, string? activeStatus, string? loginAge, string? search)
    {
        var query = _db.Users.AsNoTracking().IgnoreQueryFilters()
            .Include(u => u.Company)
            .AsQueryable();

        if (companyId.HasValue) query = query.Where(u => u.CompanyId == companyId);
        if (!string.IsNullOrWhiteSpace(role)) query = query.Where(u => u.CompanyRole == role);
        if (activeStatus == "active") query = query.Where(u => u.IsActive);
        else if (activeStatus == "inactive") query = query.Where(u => !u.IsActive);
        if (!string.IsNullOrWhiteSpace(search))
            query = query.Where(u => u.FirstName.Contains(search) || u.LastName.Contains(search) || u.Email!.Contains(search));

        var now = DateTime.UtcNow;
        if (loginAge == "never") query = query.Where(u => u.LastLoginDate == null);
        else if (loginAge == "90plus") query = query.Where(u => u.LastLoginDate == null || u.LastLoginDate < now.AddDays(-90));
        else if (loginAge == "30plus") query = query.Where(u => u.LastLoginDate == null || u.LastLoginDate < now.AddDays(-30));
        else if (loginAge == "recent") query = query.Where(u => u.LastLoginDate != null && u.LastLoginDate >= now.AddDays(-7));

        var users = await query.OrderBy(u => u.Company!.CompanyName).ThenBy(u => u.LastName).ToListAsync();

        var sb = new StringBuilder();
        sb.AppendLine("Company,Name,Email,Role,Active,Last Login,Days Since Login,Created");
        foreach (var u in users)
        {
            var days = u.LastLoginDate.HasValue ? (int)(now - u.LastLoginDate.Value).TotalDays : -1;
            sb.AppendLine(string.Join(",",
                CsvEscape(u.Company?.CompanyName ?? ""),
                CsvEscape(u.FirstName + " " + u.LastName),
                CsvEscape(u.Email ?? ""),
                CsvEscape(u.CompanyRole ?? ""),
                u.IsActive ? "Yes" : "No",
                u.LastLoginDate.HasValue ? u.LastLoginDate.Value.ToString("MM/dd/yyyy") : "Never",
                days >= 0 ? days.ToString() : "—",
                u.CreatedAt.ToString("MM/dd/yyyy")));
        }

        var bytes = Encoding.UTF8.GetBytes(sb.ToString());
        return File(bytes, "text/csv", $"user-activity-{DateTime.UtcNow:yyyyMMdd}.csv");
    }

    /// <summary>
    /// Wraps <paramref name="value"/> in double-quotes and escapes any internal
    /// double-quotes by doubling them, conforming to RFC 4180 CSV quoting rules.
    /// Values that contain no commas, quotes, or newlines are returned as-is to
    /// keep the output clean and readable.
    /// </summary>
    private static string CsvEscape(string value) =>
        value.Contains(',') || value.Contains('"') || value.Contains('\n')
            ? $"\"{value.Replace("\"", "\"\"")}\""
            : value;
}

public class OnlineUserRow
{
    public string  UserId      { get; set; } = string.Empty;
    public string  Email       { get; set; } = string.Empty;
    public string  DisplayName { get; set; } = string.Empty;
    public bool    IsSuperAdmin { get; set; }
    public string? CompanyName { get; set; }
    public int?    CompanyId   { get; set; }
    public string? Role        { get; set; }
    public string? CurrentPath { get; set; }
    public string? IpAddress   { get; set; }
    public DateTime LastSeen   { get; set; }
}

public class UserActivityRow
{
    public string Id { get; set; } = string.Empty;
    public int CompanyId { get; set; }
    public string CompanyName { get; set; } = string.Empty;
    public string FullName { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
    public string CompanyRole { get; set; } = string.Empty;
    public bool IsActive { get; set; }
    public DateTime? LastLoginDate { get; set; }
    public DateTime CreatedAt { get; set; }
    public int? DaysSinceLogin { get; set; }
}
