using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.TestSupport.Probes;

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

/// <summary>
///     Entry point for live-AVEVA test fixtures. Runs every relevant probe and returns a
///     <see cref="PrerequisiteReport"/> whose <c>SkipReason</c> feeds <c>Assert.Skip</c> when
///     the environment isn't set up. Non-Windows hosts get a single aggregated Skip row per
///     category instead of a flood of individual skips.
/// </summary>
/// <remarks>
///     <para><b>Call shape</b>:</para>
///     <code>
///         var report = await AvevaPrerequisites.CheckAllAsync();
///         if (report.SkipReason is not null) Assert.Skip(report.SkipReason);
///     </code>
///     <para><b>Categories in rough order of 'would I want to know first?'</b>:</para>
///     <list type="number">
///         <item>Environment — process bitness, OS platform, RPCSS up.</item>
///         <item>AvevaInstall — Framework registry, install paths, no pending reboot.</item>
///         <item>AvevaCoreService — aaBootstrap / aaGR / NmxSvc running.</item>
///         <item>MxAccessCom — LMXProxy.LMXProxyServer ProgID → CLSID → file-on-disk.</item>
///         <item>GalaxyRepository — SQL reachable, ZB exists, deployed-object count.</item>
///         <item>OtOpcUaService — our two Windows services + GLAuth.</item>
///         <item>AvevaSoftService — aaLogger etc., warn only.</item>
///         <item>AvevaHistorian — aahClientAccessPoint etc., optional.</item>
///     </list>
///     <para><b>What's NOT checked here</b>: end-to-end subscribe / read / write against a real
///     Galaxy tag. That's the job of the live-smoke tests this helper gates — the helper just
///     tells them whether running is worthwhile.</para>
/// </remarks>
public static class AvevaPrerequisites
{
    // -------- Individual service lists (kept as data so tests can inspect / override) --------

    /// <summary>Services whose absence means live-Galaxy tests can't run at all.</summary>
    internal static readonly (string Name, string Purpose)[] CoreServices =
    [
        ("aaBootstrap", "master service that starts the Platform process + brokers aa* communication"),
        ("aaGR", "Galaxy Repository host — mediates IDE / runtime access to ZB"),
        ("NmxSvc", "Network Message Exchange — MXAccess + Bootstrap transport"),
        ("MSSQLSERVER", "SQL Server instance that hosts the ZB database"),
    ];

    /// <summary>Warn-but-don't-fail AVEVA services.</summary>
    internal static readonly (string Name, string Purpose)[] SoftServices =
    [
        ("aaLogger", "ArchestrA Logger — diagnostic log receiver; stack runs without it but error visibility suffers"),
        ("aaUserValidator", "OS user/group auth for ArchestrA security; only required when Galaxy security mode isn't 'Open'"),
        ("aaGlobalDataCacheMonitorSvr", "cross-platform global data cache; single-node dev boxes run fine without it"),
    ];

    /// <summary>Optional AVEVA Historian services — only required for HistoryRead IPC paths.</summary>
    internal static readonly (string Name, string Purpose)[] HistorianServices =
    [
        ("aahClientAccessPoint", "AVEVA Historian Client Access Point — HistoryRead IPC endpoint"),
        ("aahGateway", "AVEVA Historian Gateway"),
    ];

    /// <summary>OtOpcUa-stack Windows services + third-party deps we manage.</summary>
    internal static readonly (string Name, string Purpose, bool HardRequired)[] OtOpcUaServices =
    [
        ("OtOpcUaGalaxyHost", "Galaxy.Host out-of-process service (net48 x86, STA + MXAccess)", true),
        ("OtOpcUa", "Main OPC UA server service (hosts Proxy + DriverHost + Admin-facing DB publisher)", false),
        ("GLAuth", "LDAP server (dev only) — glauth.exe on localhost:3893", false),
    ];

    // -------- Orchestrator --------

    public static async Task<PrerequisiteReport> CheckAllAsync(
        Options? options = null, CancellationToken ct = default)
    {
        options ??= new Options();
        var checks = new List<PrerequisiteCheck>();

        // Environment
        checks.Add(MxAccessComProbe.CheckProcessBitness());

        // AvevaInstall — registry + files
        checks.Add(RegistryProbe.CheckFrameworkInstalled());
        checks.Add(RegistryProbe.CheckPlatformDeployed());
        checks.Add(RegistryProbe.CheckRebootPending());

        // AvevaCoreService
        foreach (var (name, purpose) in CoreServices)
            checks.Add(ServiceProbe.Check(name, PrerequisiteCategory.AvevaCoreService, hardRequired: true, whatItDoes: purpose));

        // MxAccessCom
        checks.Add(MxAccessComProbe.Check());

        // GalaxyRepository
        checks.Add(await SqlProbe.CheckZbDatabaseAsync(options.SqlConnectionString, ct));
        // Deployed-object count only makes sense if the DB check passed.
        if (checks[checks.Count - 1].Status == PrerequisiteStatus.Pass)
            checks.Add(await SqlProbe.CheckDeployedObjectCountAsync(options.SqlConnectionString, ct));

        // OtOpcUaService
        foreach (var (name, purpose, hard) in OtOpcUaServices)
            checks.Add(ServiceProbe.Check(name, PrerequisiteCategory.OtOpcUaService, hardRequired: hard, whatItDoes: purpose));
        if (options.CheckGalaxyHostPipe)
            checks.Add(await NamedPipeProbe.CheckGalaxyHostPipeAsync(options.GalaxyHostPipeName, ct));

        // AvevaSoftService
        foreach (var (name, purpose) in SoftServices)
            checks.Add(ServiceProbe.Check(name, PrerequisiteCategory.AvevaSoftService, hardRequired: false, whatItDoes: purpose));

        // AvevaHistorian
        if (options.CheckHistorian)
        {
            foreach (var (name, purpose) in HistorianServices)
                checks.Add(ServiceProbe.Check(name, PrerequisiteCategory.AvevaHistorian, hardRequired: false, whatItDoes: purpose));
        }

        return new PrerequisiteReport(checks);
    }

    /// <summary>
    ///     Narrower check for tests that only need the Galaxy Repository (SQL) path — don't
    ///     pay the cost of probing every aa* service when the test only reads gobject rows.
    /// </summary>
    public static async Task<PrerequisiteReport> CheckRepositoryOnlyAsync(
        string? sqlConnectionString = null, CancellationToken ct = default)
    {
        var checks = new List<PrerequisiteCheck>
        {
            await SqlProbe.CheckZbDatabaseAsync(sqlConnectionString, ct),
        };
        if (checks[0].Status == PrerequisiteStatus.Pass)
            checks.Add(await SqlProbe.CheckDeployedObjectCountAsync(sqlConnectionString, ct));
        return new PrerequisiteReport(checks);
    }

    /// <summary>
    ///     Narrower check for the named-pipe endpoint — tests that drive the full Proxy
    ///     against a live Galaxy.Host service don't need the SQL or AVEVA-internal probes
    ///     (the Host does that work internally; we just need the pipe to accept).
    /// </summary>
    public static async Task<PrerequisiteReport> CheckGalaxyHostPipeOnlyAsync(
        string? pipeName = null, CancellationToken ct = default)
    {
        var checks = new List<PrerequisiteCheck>
        {
            await NamedPipeProbe.CheckGalaxyHostPipeAsync(pipeName, ct),
        };
        return new PrerequisiteReport(checks);
    }

    /// <summary>Knobs for <see cref="CheckAllAsync"/>.</summary>
    public sealed class Options
    {
        /// <summary>SQL Server connection string — defaults to Windows-auth <c>localhost\ZB</c>.</summary>
        public string? SqlConnectionString { get; init; }

        /// <summary>Named-pipe endpoint for OtOpcUaGalaxyHost — defaults to <c>OtOpcUaGalaxy</c>.</summary>
        public string? GalaxyHostPipeName { get; init; }

        /// <summary>Include the named-pipe probe. Off by default — it's a seconds-long TCP-like probe and some tests don't need it.</summary>
        public bool CheckGalaxyHostPipe { get; init; } = true;

        /// <summary>Include Historian service probes. Off by default — Historian is optional.</summary>
        public bool CheckHistorian { get; init; } = false;
    }
}
