using System.IO;
using FluentAssertions;
using DragonISO.App.Services;
using DragonISO.App.Tests.Fakes;

namespace DragonISO.App.Tests.Services;

// Tests for the OscBridge.DispatchAsync routing. We construct
// OscMessage instances directly (skipping the UDP receive loop) and
// assert that the right address resolves to the right controller call.
//
// The toggle / preset paths require Application.Current.Dispatcher,
// which doesn't exist in xUnit's default execution context â€” those
// paths return early on the null check, so we verify the bail rather
// than the happy path. The full toggle path is covered in branch 11's
// integration test that boots a real dispatcher.
//
// Shares NotesStateCollection with NotesServiceTests â€” both classes
// mutate NotesService.DirectoryOverride and would otherwise race.
[Collection(NotesStateCollection.Name)]
public sealed class OscBridgeDispatchTests : IDisposable
{
    private readonly string _tempNotesDir;
    private readonly string? _previousNotesOverride;

    public OscBridgeDispatchTests()
    {
        _tempNotesDir = Path.Combine(Path.GetTempPath(), $"Dragon-ISO-osc-{Guid.NewGuid():N}");
        _previousNotesOverride = NotesService.DirectoryOverride;
        NotesService.DirectoryOverride = _tempNotesDir;
    }

    public void Dispose()
    {
        NotesService.DirectoryOverride = _previousNotesOverride;
        try { if (Directory.Exists(_tempNotesDir)) Directory.Delete(_tempNotesDir, recursive: true); }
        catch { /* best-effort */ }
    }

    private static (OscBridge Bridge, StubIsoController Controller) NewBridge()
    {
        var controller = new StubIsoController();
        // OscBridge takes Func<MainViewModel?> â€” returning null exercises
        // the "no VM yet" graceful path in handlers that need it.
        var bridge = new OscBridge(controller, () => null, logger: null);
        return (bridge, controller);
    }

    [Fact]
    public async Task RefreshDiscoveryAddress_CallsControllerRefreshDiscovery()
    {
        var (bridge, controller) = NewBridge();

        await bridge.DispatchAsync(new OscMessage { Address = "/Dragon-ISO/refresh-discovery" });

        controller.RefreshDiscoveryCalled.Should().BeTrue();
    }

    [Fact]
    public async Task UnknownAddress_NoOpsCleanly()
    {
        var (bridge, controller) = NewBridge();

        await bridge.DispatchAsync(new OscMessage { Address = "/Dragon-ISO/nope/never" });

        controller.RefreshDiscoveryCalled.Should().BeFalse();
        controller.EnableCalls.Should().BeEmpty();
        controller.DisableCalls.Should().BeEmpty();
    }

    [Fact]
    public async Task NotesAddress_AppendsViaNotesService()
    {
        var (bridge, _) = NewBridge();

        await bridge.DispatchAsync(new OscMessage
        {
            Address = "/Dragon-ISO/notes",
            TypeTag = ",s",
            Args = new object[] { "tracked through OSC" },
        });

        File.Exists(NotesService.TodayPath).Should().BeTrue();
        File.ReadAllText(NotesService.TodayPath).Should().Contain("tracked through OSC");
    }

    [Fact]
    public async Task StopAllAddress_NoOpsWhenViewModelIsNull()
    {
        // Without a view-model, the stop-all path returns before touching
        // the controller. The point of this test is to pin that the bail
        // is clean â€” no thrown exception, no controller traffic.
        var (bridge, controller) = NewBridge();

        await bridge.DispatchAsync(new OscMessage { Address = "/Dragon-ISO/stop-all" });

        controller.DisableCalls.Should().BeEmpty();
    }

    [Fact]
    public async Task IsoByNameAddress_NoOpsWhenViewModelIsNull()
    {
        // /Dragon-ISO/iso "Jane" 1 â€” verifies the bail when no VM is
        // wired; doesn't fire EnableIsoAsync. The dispatcher-equipped
        // version of this round-trip lives in branch 11.
        var (bridge, controller) = NewBridge();

        await bridge.DispatchAsync(new OscMessage
        {
            Address = "/Dragon-ISO/iso",
            TypeTag = ",sT",
            Args = new object[] { "Jane", true },
        });

        controller.EnableCalls.Should().BeEmpty();
    }
}
