using System.Runtime.CompilerServices;
using ZB.MOM.WW.GalaxyRepository;

namespace ZB.MOM.WW.GalaxyRepository.Tests;

/// <summary>
///     In-memory <see cref="IGalaxyRepository"/> returning canned rowsets. Counts the heavy
///     hierarchy/attribute reads so tests can assert deploy-gated skips, and can be flipped to
///     throw so the failure path is exercisable.
/// </summary>
internal sealed class FakeGalaxyRepository : IGalaxyRepository
{
    private readonly IReadOnlyList<GalaxyHierarchyRow> _hierarchy;
    private readonly IReadOnlyList<GalaxyAttributeRow> _attributes;
    private readonly IReadOnlyList<GalaxyAlarmAttributeRow> _alarmAttributes;

    public FakeGalaxyRepository(
        IReadOnlyList<GalaxyHierarchyRow> hierarchy,
        IReadOnlyList<GalaxyAttributeRow> attributes,
        DateTime? deployTime,
        IReadOnlyList<GalaxyAlarmAttributeRow>? alarmAttributes = null)
    {
        _hierarchy = hierarchy;
        _attributes = attributes;
        _alarmAttributes = alarmAttributes ?? Array.Empty<GalaxyAlarmAttributeRow>();
        DeployTime = deployTime;
    }

    /// <summary>The deploy time returned by <see cref="GetLastDeployTimeAsync"/>; mutate to simulate a redeploy.</summary>
    public DateTime? DeployTime { get; set; }

    /// <summary>When set, every query throws this exception (simulates an unreachable database).</summary>
    public Exception? ThrowOnQuery { get; set; }

    public int HierarchyReadCount { get; private set; }

    public int AttributeReadCount { get; private set; }

    public int AlarmAttributeReadCount { get; private set; }

    public Task<bool> TestConnectionAsync(CancellationToken ct = default) =>
        ThrowOnQuery is null ? Task.FromResult(true) : throw ThrowOnQuery;

    public Task<DateTime?> GetLastDeployTimeAsync(CancellationToken ct = default)
    {
        if (ThrowOnQuery is not null)
        {
            throw ThrowOnQuery;
        }

        return Task.FromResult(DeployTime);
    }

    public Task<List<GalaxyHierarchyRow>> GetHierarchyAsync(CancellationToken ct = default)
    {
        if (ThrowOnQuery is not null)
        {
            throw ThrowOnQuery;
        }

        HierarchyReadCount++;
        return Task.FromResult(_hierarchy.ToList());
    }

    public Task<List<GalaxyAttributeRow>> GetAttributesAsync(CancellationToken ct = default)
    {
        if (ThrowOnQuery is not null)
        {
            throw ThrowOnQuery;
        }

        AttributeReadCount++;
        return Task.FromResult(_attributes.ToList());
    }

    public Task<List<GalaxyAlarmAttributeRow>> GetAlarmAttributesAsync(CancellationToken ct = default)
    {
        if (ThrowOnQuery is not null)
        {
            throw ThrowOnQuery;
        }

        AlarmAttributeReadCount++;
        return Task.FromResult(_alarmAttributes.ToList());
    }
}

/// <summary>Records published deploy events so tests can assert publication.</summary>
internal sealed class RecordingDeployNotifier : IGalaxyDeployNotifier
{
    public List<GalaxyDeployEventInfo> Published { get; } = [];

    public GalaxyDeployEventInfo? Latest { get; private set; }

    public void Publish(GalaxyDeployEventInfo info)
    {
        Published.Add(info);
        Latest = info;
    }

    public async IAsyncEnumerable<GalaxyDeployEventInfo> SubscribeAsync(
        [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        if (Latest is { } latest)
        {
            yield return latest;
        }

        await Task.CompletedTask.ConfigureAwait(false);
    }
}

/// <summary>
///     In-memory <see cref="IGalaxyHierarchySnapshotStore"/>. Pre-seed <see cref="Snapshot"/>
///     to exercise the restore path; reads <see cref="SaveAsync"/> back to assert persistence.
/// </summary>
internal sealed class FakeSnapshotStore : IGalaxyHierarchySnapshotStore
{
    public GalaxyHierarchySnapshot? Snapshot { get; set; }

    public int SaveCount { get; private set; }

    public int LoadCount { get; private set; }

    public Task SaveAsync(GalaxyHierarchySnapshot snapshot, CancellationToken cancellationToken)
    {
        SaveCount++;
        Snapshot = snapshot;
        return Task.CompletedTask;
    }

    public Task<GalaxyHierarchySnapshot?> TryLoadAsync(CancellationToken cancellationToken)
    {
        LoadCount++;
        return Task.FromResult(Snapshot);
    }
}

/// <summary>
///     A <see cref="TimeProvider"/> whose UTC clock is fixed (and advanceable) so the cache's
///     staleness projection (which fires after a 5-minute threshold) is deterministic.
/// </summary>
internal sealed class StubTimeProvider(DateTimeOffset start) : TimeProvider
{
    private DateTimeOffset _now = start;

    public override DateTimeOffset GetUtcNow() => _now;

    public void Advance(TimeSpan delta) => _now += delta;
}
