using ZB.MOM.WW.GalaxyRepository;
using ZB.MOM.WW.GalaxyRepository.Grpc;

namespace ZB.MOM.WW.MxGateway.Server.Dashboard;

/// <summary>
///     Projects a shared-library <see cref="GalaxyHierarchyCacheEntry"/> into the
///     dashboard's host-side <see cref="DashboardGalaxySummary"/>.
/// </summary>
/// <remarks>
///     The shared <c>ZB.MOM.WW.GalaxyRepository</c> cache entry does not compute a
///     dashboard summary; this projector relocates the summary logic that previously
///     lived inside the gateway's inline Galaxy hierarchy cache. It groups the entry's
///     objects into template usage and category counts, maps the cache status to the
///     dashboard status, and copies the timestamps and counts the entry already carries.
/// </remarks>
public static class DashboardGalaxySummaryProjector
{
    private const int MaxTopTemplates = 10;

    /// <summary>
    ///     Builds a <see cref="DashboardGalaxySummary"/> from a shared-library Galaxy
    ///     hierarchy cache entry.
    /// </summary>
    /// <param name="entry">The shared-library cache entry to project.</param>
    /// <returns>The dashboard summary derived from <paramref name="entry"/>.</returns>
    public static DashboardGalaxySummary Project(GalaxyHierarchyCacheEntry entry)
    {
        ArgumentNullException.ThrowIfNull(entry);
        return BuildSummary(entry, ComputeBreakdown(entry));
    }

    /// <summary>
    ///     Computes the O(N) template-usage and category breakdown from the entry's objects.
    ///     This is the only part of the summary that changes with the cache
    ///     <see cref="GalaxyHierarchyCacheEntry.Sequence"/> — the shared library replaces the object
    ///     set only on a heavy refresh that bumps the sequence — so callers may memoize the result
    ///     on the sequence. The cheap, per-tick-volatile fields are re-read fresh via
    ///     <see cref="BuildSummary"/> and must never be memoized.
    /// </summary>
    /// <param name="entry">The shared-library cache entry to project.</param>
    /// <returns>The template/category breakdown of <paramref name="entry"/>'s objects.</returns>
    public static GalaxyObjectBreakdown ComputeBreakdown(GalaxyHierarchyCacheEntry entry)
    {
        ArgumentNullException.ThrowIfNull(entry);

        if (entry.Objects.Count == 0)
        {
            return GalaxyObjectBreakdown.Empty;
        }

        Dictionary<int, int> objectsByCategory = new();
        Dictionary<string, int> templateUsage = new(StringComparer.OrdinalIgnoreCase);

        foreach (GalaxyObject obj in entry.Objects)
        {
            objectsByCategory.TryGetValue(obj.CategoryId, out int categoryCount);
            objectsByCategory[obj.CategoryId] = categoryCount + 1;

            if (obj.TemplateChain.Count > 0)
            {
                string immediate = obj.TemplateChain[0];
                if (!string.IsNullOrWhiteSpace(immediate))
                {
                    templateUsage.TryGetValue(immediate, out int templateCount);
                    templateUsage[immediate] = templateCount + 1;
                }
            }
        }

        IReadOnlyList<DashboardGalaxyTemplateUsage> topTemplates = templateUsage
            .OrderByDescending(usage => usage.Value)
            .ThenBy(usage => usage.Key, StringComparer.OrdinalIgnoreCase)
            .Take(MaxTopTemplates)
            .Select(usage => new DashboardGalaxyTemplateUsage(usage.Key, usage.Value))
            .ToArray();

        IReadOnlyList<DashboardGalaxyCategoryCount> objectCategories = objectsByCategory
            .OrderByDescending(category => category.Value)
            .ThenBy(category => category.Key)
            .Select(category => new DashboardGalaxyCategoryCount(
                category.Key,
                ResolveCategoryName(category.Key),
                category.Value))
            .ToArray();

        return new GalaxyObjectBreakdown(topTemplates, objectCategories);
    }

    /// <summary>
    ///     Assembles a <see cref="DashboardGalaxySummary"/> from an entry's cheap, per-tick-volatile
    ///     fields (status, timestamps, last error, counts) and a precomputed
    ///     <paramref name="breakdown"/>. The volatile fields are copied fresh on every call so a
    ///     memoized breakdown never freezes the dashboard's health or timestamps between heavy
    ///     refreshes — the library mutates those fields in place at the same sequence on steady-state
    ///     ticks and on refresh failure.
    /// </summary>
    /// <param name="entry">The cache entry whose volatile fields to copy.</param>
    /// <param name="breakdown">The precomputed template/category breakdown.</param>
    /// <returns>The assembled dashboard summary.</returns>
    public static DashboardGalaxySummary BuildSummary(GalaxyHierarchyCacheEntry entry, GalaxyObjectBreakdown breakdown)
    {
        ArgumentNullException.ThrowIfNull(entry);
        ArgumentNullException.ThrowIfNull(breakdown);

        return new DashboardGalaxySummary(
            Status: MapDashboardStatus(entry.Status),
            LastQueriedAt: entry.LastQueriedAt,
            LastSuccessAt: entry.LastSuccessAt,
            LastDeployTime: entry.LastDeployTime,
            LastError: entry.LastError,
            ObjectCount: entry.ObjectCount,
            AreaCount: entry.AreaCount,
            AttributeCount: entry.AttributeCount,
            HistorizedAttributeCount: entry.HistorizedAttributeCount,
            AlarmAttributeCount: entry.AlarmAttributeCount,
            TopTemplates: breakdown.TopTemplates,
            ObjectCategories: breakdown.ObjectCategories);
    }

    private static DashboardGalaxyStatus MapDashboardStatus(GalaxyCacheStatus status) => status switch
    {
        GalaxyCacheStatus.Healthy => DashboardGalaxyStatus.Healthy,
        GalaxyCacheStatus.Stale => DashboardGalaxyStatus.Stale,
        GalaxyCacheStatus.Unavailable => DashboardGalaxyStatus.Unavailable,
        _ => DashboardGalaxyStatus.Unknown,
    };

    private static string ResolveCategoryName(int categoryId) => categoryId switch
    {
        1 => "WinPlatform",
        3 => "AppEngine",
        4 => "InTouchViewApp",
        10 => "UserDefined",
        11 => "FieldReference",
        13 => "Area",
        17 => "DIObject",
        24 => "DDESuiteLinkClient",
        26 => "OPCClient",
        _ => $"Category {categoryId}",
    };
}
