using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using PowderCoating.Application.DTOs.Health;
using PowderCoating.Application.Interfaces;
using PowderCoating.Core.Enums;
using PowderCoating.Infrastructure.Data;
using PowderCoating.Shared.Constants;

namespace PowderCoating.Web.Controllers;

[Authorize(Policy = AppConstants.Policies.SuperAdminOnly)]
public class CompanyHealthController : Controller
{
    private readonly ApplicationDbContext _db;
    private readonly ICompanyConfigHealthService _configHealth;

    public CompanyHealthController(ApplicationDbContext db, ICompanyConfigHealthService configHealth)
    {
        _db = db;
        _configHealth = configHealth;
    }

    /// <summary>
    /// Renders the Company Health dashboard showing a churn-risk score and engagement
    /// signals for every tenant company (SuperAdmin only).
    /// <para>
    /// Data is collected via a series of focused, aggregated DB queries (one per signal
    /// type) rather than a single join query, because the join approach would require
    /// complex LEFT JOINs across five tables and produce a large intermediate result
    /// set. Each query groups by <c>CompanyId</c> and materialises to a dictionary for
    /// O(1) lookup when building the per-company <see cref="CompanyHealthDto"/>.
    /// </para>
    /// <para>
    /// All queries use <c>IgnoreQueryFilters()</c> so deleted companies and their
    /// data are visible to SuperAdmins. The <c>!c.IsDeleted</c> predicate on the
    /// companies query is then applied manually to exclude companies that have been
    /// permanently removed from the platform.
    /// </para>
    /// <para>
    /// Health scoring and signal classification are delegated to
    /// <see cref="ComputeHealth"/> and the result is used to assign a
    /// <see cref="ChurnRisk"/> bucket. Summary counts (for the dashboard header KPI
    /// cards) are computed from the full un-filtered list <em>before</em> applying the
    /// user's risk/search filters, so the KPI cards always show platform-wide totals.
    /// </para>
    /// </summary>
    public async Task<IActionResult> Index(string? risk, string? search, bool configIssuesOnly = false, bool showChurned = false)
    {
        var now = DateTime.UtcNow;
        var d30 = now.AddDays(-30);
        var d90 = now.AddDays(-90);
        var churnedCutoff = now.AddDays(-14);

        // One query per signal — all keyed by CompanyId
        var allCompanies = await _db.Companies
            .AsNoTracking().IgnoreQueryFilters()
            .Where(c => !c.IsDeleted)
            .ToListAsync();

        var churnedCount = allCompanies.Count(c =>
            (c.SubscriptionStatus == SubscriptionStatus.Expired || c.SubscriptionStatus == SubscriptionStatus.Canceled)
            && c.SubscriptionEndDate.HasValue && c.SubscriptionEndDate.Value < churnedCutoff);

        var companies = showChurned
            ? allCompanies
            : allCompanies.Where(c =>
                !((c.SubscriptionStatus == SubscriptionStatus.Expired || c.SubscriptionStatus == SubscriptionStatus.Canceled)
                  && c.SubscriptionEndDate.HasValue && c.SubscriptionEndDate.Value < churnedCutoff))
              .ToList();

        var lastLogins = await _db.Users
            .AsNoTracking().IgnoreQueryFilters()
            .Where(u => u.LastLoginDate != null)
            .GroupBy(u => u.CompanyId)
            .Select(g => new { CompanyId = g.Key, Last = g.Max(u => u.LastLoginDate) })
            .ToDictionaryAsync(x => x.CompanyId, x => x.Last);

        var jobs30 = await _db.Jobs
            .AsNoTracking().IgnoreQueryFilters()
            .Where(j => !j.IsDeleted && j.CreatedAt >= d30)
            .GroupBy(j => j.CompanyId)
            .Select(g => new { g.Key, Count = g.Count() })
            .ToDictionaryAsync(x => x.Key, x => x.Count);

        var jobs90 = await _db.Jobs
            .AsNoTracking().IgnoreQueryFilters()
            .Where(j => !j.IsDeleted && j.CreatedAt >= d90)
            .GroupBy(j => j.CompanyId)
            .Select(g => new { g.Key, Count = g.Count() })
            .ToDictionaryAsync(x => x.Key, x => x.Count);

        var totalJobs = await _db.Jobs
            .AsNoTracking().IgnoreQueryFilters()
            .Where(j => !j.IsDeleted)
            .GroupBy(j => j.CompanyId)
            .Select(g => new { g.Key, Count = g.Count() })
            .ToDictionaryAsync(x => x.Key, x => x.Count);

        var totalCustomers = await _db.Customers
            .AsNoTracking().IgnoreQueryFilters()
            .Where(c => !c.IsDeleted)
            .GroupBy(c => c.CompanyId)
            .Select(g => new { g.Key, Count = g.Count() })
            .ToDictionaryAsync(x => x.Key, x => x.Count);

        var totalQuotes = await _db.Quotes
            .AsNoTracking().IgnoreQueryFilters()
            .GroupBy(q => q.CompanyId)
            .Select(g => new { g.Key, Count = g.Count() })
            .ToDictionaryAsync(x => x.Key, x => x.Count);

        var planNames = await _db.SubscriptionPlanConfigs
            .AsNoTracking().IgnoreQueryFilters()
            .Where(p => p.IsActive)
            .ToDictionaryAsync(p => p.Plan, p => p.DisplayName);

        // Batch config health check — one set of queries for all companies
        var companyIds = companies.Select(c => c.Id).ToList();
        var configHealthMap = await _configHealth.CheckBatchAsync(companyIds);

        var all = companies.Select(c =>
        {
            var lastLogin = lastLogins.TryGetValue(c.Id, out var ll) ? ll : null;
            var daysSince = lastLogin.HasValue ? (int)(now - lastLogin.Value).TotalDays : -1;
            var j30v  = jobs30.TryGetValue(c.Id, out var v30) ? v30 : 0;
            var j90v  = jobs90.TryGetValue(c.Id, out var v90) ? v90 : 0;
            var tjobs = totalJobs.TryGetValue(c.Id, out var tj) ? tj : 0;
            var tcust = totalCustomers.TryGetValue(c.Id, out var tc) ? tc : 0;
            var tquotes = totalQuotes.TryGetValue(c.Id, out var tq) ? tq : 0;
            var planName = planNames.TryGetValue(c.SubscriptionPlan, out var pn) ? pn : c.SubscriptionPlan.ToString();

            var (score, signals) = CompanyHealthHelper.ComputeHealth(c, daysSince, j30v, j90v, tjobs, now);

            var neverActivated = tjobs == 0 && tcust == 0 && tquotes == 0
                                 && c.CreatedAt < now.AddDays(-7);

            var riskLevel = CompanyHealthHelper.ToRiskLevel(score, neverActivated);

            var configHealth = configHealthMap.TryGetValue(c.Id, out var ch)
                ? ch : new CompanyConfigHealth { CompanyId = c.Id };

            return new CompanyHealthDto
            {
                Id = c.Id,
                CompanyName = c.CompanyName,
                PrimaryContactEmail = c.PrimaryContactEmail,
                PlanDisplayName = planName,
                SubscriptionStatus = c.SubscriptionStatus,
                SubscriptionEndDate = c.SubscriptionEndDate,
                IsActive = c.IsActive,
                IsComped = c.IsComped,
                IsNeverActivated = neverActivated,
                CreatedAt = c.CreatedAt,
                LastLoginDate = lastLogin,
                DaysSinceLastLogin = daysSince,
                JobsLast30Days = j30v,
                JobsLast90Days = j90v,
                TotalJobs = tjobs,
                TotalCustomers = tcust,
                TotalQuotes = tquotes,
                HealthScore = score,
                RiskLevel = riskLevel,
                RiskSignals = signals,
                ConfigHealth = configHealth,
            };
        }).ToList();

        // Summary counts before filtering (always platform-wide totals)
        ViewBag.HealthyCount        = all.Count(h => h.RiskLevel == ChurnRisk.Healthy);
        ViewBag.AtRiskCount         = all.Count(h => h.RiskLevel == ChurnRisk.AtRisk);
        ViewBag.CriticalCount       = all.Count(h => h.RiskLevel == ChurnRisk.Critical);
        ViewBag.NeverActivatedCount = all.Count(h => h.RiskLevel == ChurnRisk.NeverActivated);
        ViewBag.ConfigIssuesCount   = all.Count(h => !h.ConfigHealth.IsHealthy);
        ViewBag.Risk              = risk;
        ViewBag.Search            = search;
        ViewBag.ConfigIssuesOnly  = configIssuesOnly;
        ViewBag.ShowChurned       = showChurned;
        ViewBag.ChurnedCount      = churnedCount;

        if (!string.IsNullOrWhiteSpace(search))
            all = all.Where(h =>
                h.CompanyName.Contains(search, StringComparison.OrdinalIgnoreCase) ||
                h.PrimaryContactEmail.Contains(search, StringComparison.OrdinalIgnoreCase)).ToList();

        if (!string.IsNullOrWhiteSpace(risk) && Enum.TryParse<ChurnRisk>(risk, out var riskEnum))
            all = all.Where(h => h.RiskLevel == riskEnum).ToList();

        if (configIssuesOnly)
            all = all.Where(h => !h.ConfigHealth.IsHealthy).ToList();

        // Worst first
        all = all.OrderBy(h => (int)h.RiskLevel == 3 ? 3 : 0)   // NeverActivated last
                 .ThenBy(h => h.HealthScore)
                 .ThenBy(h => h.CompanyName)
                 .ToList();

        return View(all);
    }

}

// ── View models ────────────────────────────────────────────────────────────────

public class CompanyHealthDto
{
    public int Id { get; set; }
    public string CompanyName { get; set; } = "";
    public string PrimaryContactEmail { get; set; } = "";
    public string PlanDisplayName { get; set; } = "";
    public SubscriptionStatus SubscriptionStatus { get; set; }
    public DateTime? SubscriptionEndDate { get; set; }
    public bool IsActive { get; set; }
    public bool IsComped { get; set; }
    public bool IsNeverActivated { get; set; }
    public DateTime CreatedAt { get; set; }

    public DateTime? LastLoginDate { get; set; }
    public int DaysSinceLastLogin { get; set; }  // -1 = never

    public int JobsLast30Days { get; set; }
    public int JobsLast90Days { get; set; }
    public int TotalJobs { get; set; }
    public int TotalCustomers { get; set; }
    public int TotalQuotes { get; set; }

    public int HealthScore { get; set; }
    public ChurnRisk RiskLevel { get; set; }
    public List<string> RiskSignals { get; set; } = new();

    // Configuration health (setup gaps that break features)
    public CompanyConfigHealth ConfigHealth { get; set; } = new();
}
