using System;
using System.Threading;
using System.Threading.Tasks;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend.Galaxy;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Shared.Contracts;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.TestSupport;

namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
{
    /// <summary>
    ///     Live smoke against the Galaxy <c>ZB</c> repository. Skipped when ZB is unreachable so
    ///     CI / dev boxes without an AVEVA install still pass. Exercises the ported
    ///     <see cref="GalaxyRepository"/> + <see cref="DbBackedGalaxyBackend"/> against the same
    ///     SQL the v1 Host uses, proving the lift is byte-for-byte equivalent at the
    ///     <c>DiscoverHierarchyResponse</c> shape.
    /// </summary>
    /// <remarks>
    ///     Since PR 36, skip logic is delegated to <see cref="AvevaPrerequisites.CheckRepositoryOnlyAsync"/>
    ///     so operators see exactly why a test skipped ("ZB db not found" vs "SQL Server
    ///     unreachable") instead of a silent return.
    /// </remarks>
    [Trait("Category", "LiveGalaxy")]
    public sealed class GalaxyRepositoryLiveSmokeTests
    {
        private static GalaxyRepositoryOptions DevZbOptions() => new()
        {
            ConnectionString =
                "Server=localhost;Database=ZB;Integrated Security=True;TrustServerCertificate=True;Encrypt=False;Connect Timeout=2;",
            CommandTimeoutSeconds = 10,
        };

        private static async Task<string?> RepositorySkipReasonAsync()
        {
            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(4));
            var report = await AvevaPrerequisites.CheckRepositoryOnlyAsync(
                DevZbOptions().ConnectionString, cts.Token);
            return report.SkipReason;
        }

        private static async Task<bool> ZbReachableAsync()
        {
            // Legacy silent-skip adapter — keeps the existing tests compiling while
            // gradually migrating to the Skip-with-reason pattern. Returns true when the
            // prerequisite check has no Fail entries.
            return (await RepositorySkipReasonAsync()) is null;
        }

        [Fact]
        public async Task TestConnection_returns_true_against_live_ZB()
        {
            if (!await ZbReachableAsync()) return;

            var repo = new GalaxyRepository(DevZbOptions());
            (await repo.TestConnectionAsync()).ShouldBeTrue();
        }

        [Fact]
        public async Task GetHierarchy_returns_at_least_one_deployed_gobject()
        {
            if (!await ZbReachableAsync()) return;

            var repo = new GalaxyRepository(DevZbOptions());
            var rows = await repo.GetHierarchyAsync();

            rows.Count.ShouldBeGreaterThan(0,
                "the dev Galaxy has at least the WinPlatform + AppEngine deployed");
            rows.ShouldAllBe(r => !string.IsNullOrEmpty(r.TagName));
        }

        [Fact]
        public async Task GetAttributes_returns_attributes_for_deployed_objects()
        {
            if (!await ZbReachableAsync()) return;

            var repo = new GalaxyRepository(DevZbOptions());
            var attrs = await repo.GetAttributesAsync();

            attrs.Count.ShouldBeGreaterThan(0);
            attrs.ShouldAllBe(a => !string.IsNullOrEmpty(a.FullTagReference) && a.FullTagReference.Contains("."));
        }

        [Fact]
        public async Task GetLastDeployTime_returns_a_value()
        {
            if (!await ZbReachableAsync()) return;

            var repo = new GalaxyRepository(DevZbOptions());
            var ts = await repo.GetLastDeployTimeAsync();
            ts.ShouldNotBeNull();
        }

        [Fact]
        public async Task DbBackedBackend_DiscoverAsync_returns_objects_with_attributes_and_categories()
        {
            if (!await ZbReachableAsync()) return;

            var backend = new DbBackedGalaxyBackend(new GalaxyRepository(DevZbOptions()));
            var resp = await backend.DiscoverAsync(new DiscoverHierarchyRequest { SessionId = 1 }, CancellationToken.None);

            resp.Success.ShouldBeTrue(resp.Error);
            resp.Objects.Length.ShouldBeGreaterThan(0);

            var firstWithAttrs = System.Linq.Enumerable.FirstOrDefault(resp.Objects, o => o.Attributes.Length > 0);
            firstWithAttrs.ShouldNotBeNull("at least one gobject in the dev Galaxy carries dynamic attributes");
            firstWithAttrs!.TemplateCategory.ShouldNotBeNullOrEmpty();
        }
    }
}
