using System.Reactive.Linq;
using System.Reactive.Subjects;
using DragonISO.Engine.Controller;
using DragonISO.Engine.Domain;
using DragonISO.Engine.Pipeline;

namespace DragonISO.App.Tests.Fakes;

// Minimal IIsoController stub for tests that need to instantiate
// services in the App layer (ControlSurfaceServer, OscBridge, etc.)
// without spinning up the real engine + NDI runtime.
//
// Everything is a sensible no-op default; tests that need a specific
// behaviour (e.g. "EnableIsoAsync was called with these args") subclass
// or replace methods via the action hooks.
internal sealed class StubIsoController : IIsoController
{
    private readonly BehaviorSubject<IReadOnlyList<Participant>> _participants =
        new(Array.Empty<Participant>());
    private readonly BehaviorSubject<EngineAlert?> _alerts = new(default);

    public IObservable<IReadOnlyList<Participant>> Participants => _participants;
    public IObservable<EngineAlert> Alerts => _alerts.Where(a => a is not null)!;

    public FrameProcessingSettings GlobalSettings { get; set; } = new(
        TargetFramerate.Fps30, TargetResolution.R1080p, AspectMode.Letterbox, AudioMode.Auto);

    public NdiGroupSettings GroupSettings { get; set; } = new(
        DiscoveryGroups: null, OutputGroups: null);

    public bool RecordingEnabled { get; private set; }
    public string? RecordingDirectory { get; private set; }

    public Func<Guid, IsoHealthStats>? GetStatsHandler { get; set; }
    public Func<Guid, ProcessedFrame?>? GetLatestProcessedFrameHandler { get; set; }
    public Func<Guid, FrameProcessingSettings?>? GetIsoOverrideHandler { get; set; }

    public IsoHealthStats GetStats(Guid participantId) =>
        GetStatsHandler?.Invoke(participantId) ?? IsoHealthStats.Empty;

    public ProcessedFrame? GetLatestProcessedFrame(Guid participantId) =>
        GetLatestProcessedFrameHandler?.Invoke(participantId);

    public FrameProcessingSettings? GetIsoOverride(Guid participantId) =>
        GetIsoOverrideHandler?.Invoke(participantId);

    public List<(Guid Id, string? Name)> EnableCalls { get; } = new();
    public List<Guid> DisableCalls { get; } = new();

    public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;

    public Task EnableIsoAsync(Guid participantId, string? customName, CancellationToken cancellationToken)
    {
        EnableCalls.Add((participantId, customName));
        return Task.CompletedTask;
    }

    public Task EnableIsoAsync(Guid participantId, string? customName, bool? recordOverride, CancellationToken cancellationToken)
    {
        EnableCalls.Add((participantId, customName));
        return Task.CompletedTask;
    }

    public Task DisableIsoAsync(Guid participantId, CancellationToken cancellationToken)
    {
        DisableCalls.Add(participantId);
        return Task.CompletedTask;
    }

    public Task SetGlobalSettingsAsync(FrameProcessingSettings settings, CancellationToken cancellationToken)
    {
        GlobalSettings = settings;
        return Task.CompletedTask;
    }

    public Task SetIsoOverrideAsync(Guid participantId, FrameProcessingSettings? settings, CancellationToken cancellationToken) =>
        Task.CompletedTask;

    public Task SetGroupSettingsAsync(NdiGroupSettings groupSettings, CancellationToken cancellationToken)
    {
        GroupSettings = groupSettings;
        return Task.CompletedTask;
    }

    public bool RefreshDiscoveryCalled { get; private set; }
    public void RefreshDiscovery() => RefreshDiscoveryCalled = true;

    public void SetRecording(bool enabled, string? outputDirectory)
    {
        RecordingEnabled = enabled;
        RecordingDirectory = outputDirectory;
    }

    public void AddRecordingMarker(string label) { /* no-op for stub */ }

    public ValueTask DisposeAsync()
    {
        _participants.Dispose();
        _alerts.Dispose();
        return ValueTask.CompletedTask;
    }

    // Used by tests to push synthetic participant snapshots through the
    // observable chain.
    public void PublishParticipants(params Participant[] participants) =>
        _participants.OnNext(participants);
}
