using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MessagePack;
using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts;

namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Backend;

/// <summary>
///     In-memory <see cref="IFocasBackend"/> for tests + an operational stub mode when
///     <c>OTOPCUA_FOCAS_BACKEND=fake</c>. Keeps per-address values keyed by a canonical
///     string; RMW semantics honor PMC bit-writes against the containing byte so the
///     <c>PmcBitWriteRequest</c> path can be exercised end-to-end without hardware.
/// </summary>
public sealed class FakeFocasBackend : IFocasBackend
{
    private readonly object _gate = new();
    private long _nextSessionId;
    private readonly HashSet<long> _openSessions = [];
    private readonly Dictionary<string, byte[]> _pmcValues = [];
    private readonly Dictionary<string, byte[]> _paramValues = [];
    private readonly Dictionary<string, byte[]> _macroValues = [];

    public Task<OpenSessionResponse> OpenSessionAsync(OpenSessionRequest request, CancellationToken ct)
    {
        lock (_gate)
        {
            var id = ++_nextSessionId;
            _openSessions.Add(id);
            return Task.FromResult(new OpenSessionResponse { Success = true, SessionId = id });
        }
    }

    public Task CloseSessionAsync(CloseSessionRequest request, CancellationToken ct)
    {
        lock (_gate) { _openSessions.Remove(request.SessionId); }
        return Task.CompletedTask;
    }

    public Task<ReadResponse> ReadAsync(ReadRequest request, CancellationToken ct)
    {
        lock (_gate)
        {
            if (!_openSessions.Contains(request.SessionId))
                return Task.FromResult(new ReadResponse { Success = false, StatusCode = 0x80020000u, Error = "session-not-open" });

            var store = StoreFor(request.Address.Kind);
            var key = CanonicalKey(request.Address);
            store.TryGetValue(key, out var value);
            return Task.FromResult(new ReadResponse
            {
                Success = true,
                StatusCode = 0,
                ValueBytes = value ?? MessagePackSerializer.Serialize((int)0),
                ValueTypeCode = request.DataType,
                SourceTimestampUtcUnixMs = System.DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
            });
        }
    }

    public Task<WriteResponse> WriteAsync(WriteRequest request, CancellationToken ct)
    {
        lock (_gate)
        {
            if (!_openSessions.Contains(request.SessionId))
                return Task.FromResult(new WriteResponse { Success = false, StatusCode = 0x80020000u, Error = "session-not-open" });

            var store = StoreFor(request.Address.Kind);
            store[CanonicalKey(request.Address)] = request.ValueBytes ?? [];
            return Task.FromResult(new WriteResponse { Success = true, StatusCode = 0 });
        }
    }

    public Task<PmcBitWriteResponse> PmcBitWriteAsync(PmcBitWriteRequest request, CancellationToken ct)
    {
        lock (_gate)
        {
            if (!_openSessions.Contains(request.SessionId))
                return Task.FromResult(new PmcBitWriteResponse { Success = false, StatusCode = 0x80020000u, Error = "session-not-open" });
            if (request.BitIndex is < 0 or > 7)
                return Task.FromResult(new PmcBitWriteResponse { Success = false, StatusCode = 0x803C0000u, Error = "bit-out-of-range" });

            var key = CanonicalKey(request.Address);
            _pmcValues.TryGetValue(key, out var current);
            current ??= MessagePackSerializer.Serialize((byte)0);
            var b = MessagePackSerializer.Deserialize<byte>(current);
            var mask = (byte)(1 << request.BitIndex);
            b = request.Value ? (byte)(b | mask) : (byte)(b & ~mask);
            _pmcValues[key] = MessagePackSerializer.Serialize(b);
            return Task.FromResult(new PmcBitWriteResponse { Success = true, StatusCode = 0 });
        }
    }

    public Task<ProbeResponse> ProbeAsync(ProbeRequest request, CancellationToken ct)
    {
        lock (_gate)
        {
            return Task.FromResult(new ProbeResponse
            {
                Healthy = _openSessions.Contains(request.SessionId),
                ObservedAtUtcUnixMs = System.DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
            });
        }
    }

    private Dictionary<string, byte[]> StoreFor(int kind) => kind switch
    {
        0 => _pmcValues,
        1 => _paramValues,
        2 => _macroValues,
        _ => _pmcValues,
    };

    private static string CanonicalKey(FocasAddressDto addr) =>
        addr.Kind switch
        {
            0 => $"{addr.PmcLetter}{addr.Number}",
            1 => $"P{addr.Number}",
            2 => $"M{addr.Number}",
            _ => $"?{addr.Number}",
        };
}
