using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using S7NetDataType = global::S7.Net.DataType;

namespace ZB.MOM.WW.OtOpcUa.Driver.S7.Tests;

/// <summary>
///     Shared in-memory <see cref="IAddressSpaceBuilder"/> recorder used across the S7 driver
///     test suite. Captures every folder + variable the driver projects via
///     <see cref="S7Driver.DiscoverAsync"/> so a test can assert the discovered shape without a
///     live address space. Replaces the three structurally-identical per-file recorders
///     (<c>RecordingAddressSpaceBuilder</c>/<c>RecordingBuilder</c>/<c>CapturingBuilder</c>) that
///     previously duplicated this code.
/// </summary>
internal sealed class RecordingAddressSpaceBuilder : IAddressSpaceBuilder
{
    /// <summary>Browse names of every folder the driver created, in order.</summary>
    public readonly List<string> Folders = new();

    /// <summary>Every variable the driver created, as (browse name, attribute info) pairs.</summary>
    public readonly List<(string Name, DriverAttributeInfo Attr)> Variables = new();

    /// <summary>Records the folder browse name and returns this builder for chaining.</summary>
    /// <param name="browseName">The browse name of the folder.</param>
    /// <param name="displayName">The display name of the folder.</param>
    /// <returns>This builder instance for method chaining.</returns>
    public IAddressSpaceBuilder Folder(string browseName, string displayName)
    {
        Folders.Add(browseName);
        return this;
    }

    /// <summary>Records the variable's browse name + attribute info.</summary>
    /// <param name="browseName">The browse name of the variable.</param>
    /// <param name="displayName">The display name of the variable.</param>
    /// <param name="attributeInfo">The attribute information for the variable.</param>
    /// <returns>A stub handle.</returns>
    public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo attributeInfo)
    {
        Variables.Add((browseName, attributeInfo));
        return new StubHandle();
    }

    /// <summary>No-op property sink — the S7 driver does not add properties.</summary>
    /// <param name="browseName">The browse name of the property.</param>
    /// <param name="dataType">The data type of the property.</param>
    /// <param name="value">The initial value of the property.</param>
    public void AddProperty(string browseName, DriverDataType dataType, object? value) { }

    /// <summary>No-op alarm sink — the S7 driver never surfaces alarms.</summary>
    /// <param name="sourceVariable">The variable to attach the alarm to.</param>
    /// <param name="alarmName">The name of the alarm.</param>
    /// <param name="alarmInfo">The alarm information.</param>
    public void AttachAlarmCondition(IVariableHandle sourceVariable, string alarmName, DriverAttributeInfo alarmInfo) { }

    private sealed class StubHandle : IVariableHandle
    {
        /// <summary>Gets the full reference of the variable.</summary>
        public string FullReference => "stub";

        /// <summary>Marks this variable as an alarm condition.</summary>
        /// <param name="info">The alarm condition information.</param>
        /// <returns>An alarm condition sink.</returns>
        public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info)
            => throw new NotImplementedException("S7 driver never calls this — no alarm surfacing");
    }
}

/// <summary>
///     Minimal <see cref="IS7PlcFactory"/> whose connection opens cleanly and answers the CPU-status
///     probe, but serves no data. Used by the tests that need the driver <c>Initialize</c>d — which in
///     v3 is what builds the RawPath → definition table <see cref="S7Driver.DiscoverAsync"/> projects —
///     without a live PLC or wire I/O.
/// </summary>
internal sealed class ConnectingFakeS7PlcFactory : IS7PlcFactory
{
    /// <summary>Creates a fresh connecting fake connection.</summary>
    public IS7Plc Create(S7CpuType cpuType, string host, int port, short rack, short slot, TimeSpan timeout)
        => new ConnectingFakeS7Plc();

    private sealed class ConnectingFakeS7Plc : IS7Plc
    {
        /// <summary>Gets a value indicating whether the fake connection is open.</summary>
        public bool IsConnected { get; private set; }
        /// <summary>Opens the fake connection.</summary>
        public Task OpenAsync(CancellationToken ct) { IsConnected = true; return Task.CompletedTask; }
        /// <summary>Closes the fake connection.</summary>
        public void Close() => IsConnected = false;
        /// <summary>Not exercised by these tests.</summary>
        public Task<object?> ReadAsync(string address, CancellationToken ct)
            => throw new NotSupportedException("ConnectingFakeS7Plc serves no data");
        /// <summary>Not exercised by these tests.</summary>
        public Task<byte[]> ReadBytesAsync(S7NetDataType area, int db, int startByteAdr, int count, CancellationToken ct)
            => throw new NotSupportedException("ConnectingFakeS7Plc serves no data");
        /// <summary>Not exercised by these tests.</summary>
        public Task WriteAsync(string address, object value, CancellationToken ct)
            => throw new NotSupportedException("ConnectingFakeS7Plc serves no data");
        /// <summary>Not exercised by these tests.</summary>
        public Task WriteBytesAsync(S7NetDataType area, int db, int startByteAdr, byte[] value, CancellationToken ct)
            => throw new NotSupportedException("ConnectingFakeS7Plc serves no data");
        /// <summary>Answers the CPU-status probe.</summary>
        public Task ReadStatusAsync(CancellationToken ct) => Task.CompletedTask;
        /// <summary>Disposes the fake connection.</summary>
        public void Dispose() => IsConnected = false;
    }
}
