using ZB.MOM.WW.OtOpcUa.Driver.FOCAS;

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

internal class FakeFocasClient : IFocasClient
{
    public bool IsConnected { get; private set; }
    public int ConnectCount { get; private set; }
    public int DisposeCount { get; private set; }
    public bool ThrowOnConnect { get; set; }
    public bool ThrowOnRead { get; set; }
    public bool ThrowOnWrite { get; set; }
    public bool ProbeResult { get; set; } = true;
    public Exception? Exception { get; set; }

    public Dictionary<string, object?> Values { get; } = new(StringComparer.OrdinalIgnoreCase);
    public Dictionary<string, uint> ReadStatuses { get; } = new(StringComparer.OrdinalIgnoreCase);
    public Dictionary<string, uint> WriteStatuses { get; } = new(StringComparer.OrdinalIgnoreCase);
    public List<(FocasAddress addr, FocasDataType type, object? value)> WriteLog { get; } = new();

    public virtual Task ConnectAsync(FocasHostAddress address, TimeSpan timeout, CancellationToken ct)
    {
        ConnectCount++;
        if (ThrowOnConnect) throw Exception ?? new InvalidOperationException();
        IsConnected = true;
        return Task.CompletedTask;
    }

    public virtual Task<(object? value, uint status)> ReadAsync(
        FocasAddress address, FocasDataType type, CancellationToken ct)
    {
        if (ThrowOnRead) throw Exception ?? new InvalidOperationException();
        var key = address.Canonical;
        var status = ReadStatuses.TryGetValue(key, out var s) ? s : FocasStatusMapper.Good;
        var value = Values.TryGetValue(key, out var v) ? v : null;
        return Task.FromResult((value, status));
    }

    public virtual Task<uint> WriteAsync(
        FocasAddress address, FocasDataType type, object? value, CancellationToken ct)
    {
        if (ThrowOnWrite) throw Exception ?? new InvalidOperationException();
        WriteLog.Add((address, type, value));
        Values[address.Canonical] = value;
        var status = WriteStatuses.TryGetValue(address.Canonical, out var s) ? s : FocasStatusMapper.Good;
        return Task.FromResult(status);
    }

    public virtual Task<bool> ProbeAsync(CancellationToken ct) => Task.FromResult(ProbeResult);

    public virtual void Dispose()
    {
        DisposeCount++;
        IsConnected = false;
    }
}

internal sealed class FakeFocasClientFactory : IFocasClientFactory
{
    public List<FakeFocasClient> Clients { get; } = new();
    public Func<FakeFocasClient>? Customise { get; set; }

    public IFocasClient Create()
    {
        var c = Customise?.Invoke() ?? new FakeFocasClient();
        Clients.Add(c);
        return c;
    }
}
