using System.Text;

namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.TestSupport;

/// <summary>
///     Aggregated result of an <see cref="AvevaPrerequisites.CheckAll"/> run. Test fixtures
///     typically call <see cref="SkipReason"/> to produce the argument for xUnit's
///     <c>Assert.Skip</c> when any hard dependency failed.
/// </summary>
public sealed class PrerequisiteReport
{
    public IReadOnlyList<PrerequisiteCheck> Checks { get; }

    public PrerequisiteReport(IEnumerable<PrerequisiteCheck> checks)
    {
        Checks = [.. checks];
    }

    /// <summary>True when every probe is Pass / Warn / Skip — no Fail entries.</summary>
    public bool IsLivetestReady => !Checks.Any(c => c.Status == PrerequisiteStatus.Fail);

    /// <summary>
    ///     True when only the AVEVA-side probes pass — ignores failures in the
    ///     <see cref="PrerequisiteCategory.OtOpcUaService"/> category. Lets a live-test gate
    ///     say "AVEVA is ready even if the v2 services aren't installed yet" without
    ///     conflating the two. Useful for tests that exercise Galaxy directly (e.g.
    ///     <see cref="GalaxyRepositoryLiveSmokeTests"/>) rather than through our stack.
    /// </summary>
    public bool IsAvevaSideReady =>
        !Checks.Any(c => c.Status == PrerequisiteStatus.Fail && c.Category != PrerequisiteCategory.OtOpcUaService);

    /// <summary>
    ///     Multi-line message for <c>Assert.Skip</c> when a hard dependency isn't met. Returns
    ///     null when <see cref="IsLivetestReady"/> is true.
    /// </summary>
    public string? SkipReason
    {
        get
        {
            var fails = Checks.Where(c => c.Status == PrerequisiteStatus.Fail).ToList();
            if (fails.Count == 0) return null;

            var sb = new StringBuilder();
            sb.AppendLine($"Live-AVEVA prerequisites not met ({fails.Count} failed):");
            foreach (var f in fails)
                sb.AppendLine($"  • [{f.Category}] {f.Name} — {f.Detail}");
            sb.Append("Run `Get-Service aa*` / `sqlcmd -S localhost -d ZB -E -Q \"SELECT 1\"` to triage.");
            return sb.ToString();
        }
    }

    /// <summary>
    ///     Human-readable summary of warnings — caller decides whether to log or ignore. Useful
    ///     when a live test does pass but an operator should know their environment is degraded.
    /// </summary>
    public string? Warnings
    {
        get
        {
            var warns = Checks.Where(c => c.Status == PrerequisiteStatus.Warn).ToList();
            if (warns.Count == 0) return null;

            var sb = new StringBuilder();
            sb.AppendLine($"AVEVA prerequisites with warnings ({warns.Count}):");
            foreach (var w in warns)
                sb.AppendLine($"  • [{w.Category}] {w.Name} — {w.Detail}");
            return sb.ToString();
        }
    }

    /// <summary>
    ///     Throw <see cref="InvalidOperationException"/> if any <paramref name="categories"/>
    ///     contain a Fail — useful when a specific test needs, say, Galaxy Repository but doesn't
    ///     care about Historian. Call before <c>Assert.Skip</c> if you want to be strict.
    /// </summary>
    public void RequireCategories(params PrerequisiteCategory[] categories)
    {
        var set = categories.ToHashSet();
        var fails = Checks.Where(c => c.Status == PrerequisiteStatus.Fail && set.Contains(c.Category)).ToList();
        if (fails.Count == 0) return;

        var detail = string.Join("; ", fails.Select(f => $"{f.Name}: {f.Detail}"));
        throw new InvalidOperationException($"Required prerequisite categories failed: {detail}");
    }

    public override string ToString()
    {
        var sb = new StringBuilder();
        sb.AppendLine($"PrerequisiteReport: {Checks.Count} checks");
        foreach (var c in Checks)
            sb.AppendLine($"  [{c.Status,-4}] {c.Category}/{c.Name}: {c.Detail}");
        return sb.ToString();
    }
}
