using System;
using System.Collections.Generic;
using ZB.MOM.WW.MxGateway.Worker.MxAccess;

namespace ZB.MOM.WW.MxGateway.Worker.Tests.MxAccess;

/// <summary>
/// Worker-007 regression tests for <see cref="MxAccessComServer"/>. The
/// adapter no longer falls back to late-bound <c>Type.InvokeMember</c>
/// reflection: a COM object must implement either the typed
/// <c>ILMXProxyServer</c> COM interface family (production) or
/// <see cref="IMxAccessServer"/> directly (test fakes).
/// </summary>
public sealed class MxAccessComServerTests
{
    /// <summary>
    /// A COM object implementing <see cref="IMxAccessServer"/> is routed
    /// through the typed interface — no reflection — preserving arguments
    /// and return values.
    /// </summary>
    [Fact]
    public void Methods_WithTypedServer_RouteThroughTypedInterface()
    {
        RecordingMxAccessServer typed = new(registerHandle: 77);
        MxAccessComServer adapter = new(typed);

        int serverHandle = adapter.Register("client-a");
        adapter.Advise(serverHandle, itemHandle: 9);
        adapter.Unregister(serverHandle);

        Assert.Equal(77, serverHandle);
        Assert.Equal("client-a", typed.RegisteredClientName);
        Assert.Equal(new[] { "Register:client-a", "Advise:77:9", "Unregister:77" }, typed.Calls);
    }

    /// <summary>
    /// A COM object that implements neither the typed COM interface family
    /// nor <see cref="IMxAccessServer"/> fails fast with a clear
    /// <see cref="InvalidOperationException"/> instead of a late-bound
    /// reflection call.
    /// </summary>
    [Fact]
    public void Methods_WithUntypedObject_ThrowInvalidOperation()
    {
        MxAccessComServer adapter = new(new object());

        InvalidOperationException exception =
            Assert.Throws<InvalidOperationException>(() => adapter.Register("client"));

        Assert.Contains("does not implement", exception.Message, StringComparison.Ordinal);
        Assert.Contains(nameof(IMxAccessServer), exception.Message, StringComparison.Ordinal);
    }

    /// <summary>
    /// Exceptions thrown by the typed server propagate unchanged — no
    /// <c>TargetInvocationException</c> wrapping (reflection is gone).
    /// </summary>
    [Fact]
    public void Methods_WhenTypedServerThrows_PropagateOriginalException()
    {
        RecordingMxAccessServer typed = new(registerHandle: 1)
        {
            ThrowOnRegister = new InvalidOperationException("register failed"),
        };
        MxAccessComServer adapter = new(typed);

        InvalidOperationException exception =
            Assert.Throws<InvalidOperationException>(() => adapter.Register("client"));

        Assert.Equal("register failed", exception.Message);
    }

    private sealed class RecordingMxAccessServer : IMxAccessServer
    {
        private readonly int registerHandle;
        private readonly List<string> calls = new();

        public RecordingMxAccessServer(int registerHandle)
        {
            this.registerHandle = registerHandle;
        }

        public string? RegisteredClientName { get; private set; }

        public Exception? ThrowOnRegister { get; set; }

        public IReadOnlyList<string> Calls => calls.ToArray();

        public int Register(string clientName)
        {
            calls.Add($"Register:{clientName}");
            RegisteredClientName = clientName;
            if (ThrowOnRegister is not null)
            {
                throw ThrowOnRegister;
            }

            return registerHandle;
        }

        public void Unregister(int serverHandle)
        {
            calls.Add($"Unregister:{serverHandle}");
        }

        public int AddItem(int serverHandle, string itemDefinition)
        {
            calls.Add($"AddItem:{serverHandle}:{itemDefinition}");
            return 0;
        }

        public int AddItem2(int serverHandle, string itemDefinition, string itemContext)
        {
            calls.Add($"AddItem2:{serverHandle}:{itemDefinition}:{itemContext}");
            return 0;
        }

        public void RemoveItem(int serverHandle, int itemHandle)
        {
            calls.Add($"RemoveItem:{serverHandle}:{itemHandle}");
        }

        public void Advise(int serverHandle, int itemHandle)
        {
            calls.Add($"Advise:{serverHandle}:{itemHandle}");
        }

        public void UnAdvise(int serverHandle, int itemHandle)
        {
            calls.Add($"UnAdvise:{serverHandle}:{itemHandle}");
        }

        public void AdviseSupervisory(int serverHandle, int itemHandle)
        {
            calls.Add($"AdviseSupervisory:{serverHandle}:{itemHandle}");
        }

        public void Write(int serverHandle, int itemHandle, object? value, int userId)
        {
            calls.Add($"Write:{serverHandle}:{itemHandle}:{value}:{userId}");
        }

        public void Write2(int serverHandle, int itemHandle, object? value, object? timestamp, int userId)
        {
            calls.Add($"Write2:{serverHandle}:{itemHandle}:{value}:{timestamp}:{userId}");
        }

        public void WriteSecured(int serverHandle, int itemHandle, int currentUserId, int verifierUserId, object? value)
        {
            calls.Add($"WriteSecured:{serverHandle}:{itemHandle}:{currentUserId}:{verifierUserId}:{value}");
        }

        public void WriteSecured2(
            int serverHandle, int itemHandle, int currentUserId, int verifierUserId, object? value, object? timestamp)
        {
            calls.Add($"WriteSecured2:{serverHandle}:{itemHandle}:{currentUserId}:{verifierUserId}:{value}:{timestamp}");
        }
    }
}
