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.Host.Backend.MxAccess;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Sta;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Shared.Contracts;

namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
{
    /// <summary>
    ///     End-to-end smoke against the live MXAccess COM runtime + Galaxy ZB DB on this dev box.
    ///     Skipped when ArchestrA bootstrap (<c>aaBootstrap</c>) isn't running. Verifies the
    ///     ported <see cref="MxAccessClient"/> can connect to <c>LMXProxyServer</c>, the
    ///     <see cref="MxAccessGalaxyBackend"/> can answer Discover against the live ZB schema,
    ///     and a one-shot read returns a valid VTQ for the first deployed attribute it finds.
    /// </summary>
    [Trait("Category", "LiveMxAccess")]
    public sealed class MxAccessLiveSmokeTests
    {
        private static GalaxyRepositoryOptions DevZb() => new()
        {
            ConnectionString = "Server=localhost;Database=ZB;Integrated Security=True;TrustServerCertificate=True;Encrypt=False;Connect Timeout=2;",
            CommandTimeoutSeconds = 10,
        };

        private static async Task<bool> ArchestraReachableAsync()
        {
            try
            {
                var repo = new GalaxyRepository(DevZb());
                using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
                if (!await repo.TestConnectionAsync(cts.Token)) return false;

                using var sc = new System.ServiceProcess.ServiceController("aaBootstrap");
                return sc.Status == System.ServiceProcess.ServiceControllerStatus.Running;
            }
            catch { return false; }
        }

        [Fact]
        public async Task Connect_to_local_LMXProxyServer_succeeds()
        {
            if (!await ArchestraReachableAsync()) return;

            using var pump = new StaPump("MxA-test-pump");
            await pump.WaitForStartedAsync();

            using var mx = new MxAccessClient(pump, new MxProxyAdapter(), "OtOpcUa-MxAccessSmoke");
            var handle = await mx.ConnectAsync();
            handle.ShouldBeGreaterThan(0);
            mx.IsConnected.ShouldBeTrue();
        }

        [Fact]
        public async Task Backend_OpenSession_then_Discover_returns_objects_with_attributes()
        {
            if (!await ArchestraReachableAsync()) return;

            using var pump = new StaPump("MxA-test-pump");
            await pump.WaitForStartedAsync();
            using var mx = new MxAccessClient(pump, new MxProxyAdapter(), "OtOpcUa-MxAccessSmoke");
            var backend = new MxAccessGalaxyBackend(new GalaxyRepository(DevZb()), mx);

            var session = await backend.OpenSessionAsync(new OpenSessionRequest { DriverInstanceId = "smoke" }, CancellationToken.None);
            session.Success.ShouldBeTrue(session.Error);

            var resp = await backend.DiscoverAsync(new DiscoverHierarchyRequest { SessionId = session.SessionId }, CancellationToken.None);
            resp.Success.ShouldBeTrue(resp.Error);
            resp.Objects.Length.ShouldBeGreaterThan(0);

            await backend.CloseSessionAsync(new CloseSessionRequest { SessionId = session.SessionId }, CancellationToken.None);
        }

        /// <summary>
        ///     Live one-shot read against any attribute we discover. Best-effort — passes silently
        ///     if no readable attribute is exposed (some Galaxy installs are configuration-only;
        ///     we only assert the call shape is correct, not a specific value).
        /// </summary>
        [Fact]
        public async Task Backend_ReadValues_against_discovered_attribute_returns_a_response_shape()
        {
            if (!await ArchestraReachableAsync()) return;

            using var pump = new StaPump("MxA-test-pump");
            await pump.WaitForStartedAsync();
            using var mx = new MxAccessClient(pump, new MxProxyAdapter(), "OtOpcUa-MxAccessSmoke");
            var backend = new MxAccessGalaxyBackend(new GalaxyRepository(DevZb()), mx);

            var session = await backend.OpenSessionAsync(new OpenSessionRequest { DriverInstanceId = "smoke" }, CancellationToken.None);
            var disc = await backend.DiscoverAsync(new DiscoverHierarchyRequest { SessionId = session.SessionId }, CancellationToken.None);
            var firstAttr = System.Linq.Enumerable.FirstOrDefault(disc.Objects, o => o.Attributes.Length > 0);
            if (firstAttr is null)
            {
                await backend.CloseSessionAsync(new CloseSessionRequest { SessionId = session.SessionId }, CancellationToken.None);
                return;
            }

            var fullRef = $"{firstAttr.TagName}.{firstAttr.Attributes[0].AttributeName}";
            var read = await backend.ReadValuesAsync(
                new ReadValuesRequest { SessionId = session.SessionId, TagReferences = new[] { fullRef } },
                CancellationToken.None);

            read.Success.ShouldBeTrue();
            read.Values.Length.ShouldBe(1);
            // We don't assert the value (it may be Bad/Uncertain depending on what's running);
            // we only assert the response shape is correct end-to-end.
            read.Values[0].TagReference.ShouldBe(fullRef);

            await backend.CloseSessionAsync(new CloseSessionRequest { SessionId = session.SessionId }, CancellationToken.None);
        }
    }
}
