using RioJoy.Core.Profiles;
using Xunit;

namespace RioJoy.Core.Tests.Profiles;

public class AutoSwitchTests
{
    private static AppConfig Config() => new()
    {
        NativeGameExecutables = { "firestorm.exe", "RedPlanet" },
        Profiles =
        {
            new RioProfile { Name = "Doom", MatchExecutables = { "doom.exe", "doom2" } },
            new RioProfile { Name = "Quake", MatchExecutables = { "quake.exe" } },
        },
    };

    [Theory]
    [InlineData("firestorm.exe")]
    [InlineData("FIRESTORM.EXE")]
    [InlineData(@"C:\games\Firestorm\firestorm.exe")]
    [InlineData("redplanet.exe")] // configured without extension, matched with one
    public void NativeGame_Yields(string exe)
    {
        Assert.Equal(SwitchMode.Yield, AutoSwitchResolver.Resolve(Config(), exe).Mode);
    }

    [Fact]
    public void SupportedGame_Activates_ItsProfile()
    {
        SwitchDecision d = AutoSwitchResolver.Resolve(Config(), @"D:\Games\doom.exe");
        Assert.Equal(SwitchMode.Activate, d.Mode);
        Assert.Equal("Doom", d.Profile!.Name);
    }

    [Fact]
    public void Unknown_IsIdle_WhenNoNeutral()
    {
        Assert.Equal(SwitchMode.Idle, AutoSwitchResolver.Resolve(Config(), "explorer.exe").Mode);
        Assert.Equal(SwitchMode.Idle, AutoSwitchResolver.Resolve(Config(), null).Mode);
    }

    [Fact]
    public void Unknown_ActivatesNeutral_WhenConfigured()
    {
        AppConfig config = Config();
        config.NeutralProfileName = "Doom";

        SwitchDecision d = AutoSwitchResolver.Resolve(config, "explorer.exe");
        Assert.Equal(SwitchMode.Activate, d.Mode);
        Assert.Equal("Doom", d.Profile!.Name);
    }

    [Fact]
    public void Native_Wins_OverProfileMatch()
    {
        AppConfig config = Config();
        // A profile also claims the native exe; native yield must still win.
        config.Profiles[0].MatchExecutables.Add("firestorm.exe");
        Assert.Equal(SwitchMode.Yield, AutoSwitchResolver.Resolve(config, "firestorm.exe").Mode);
    }

    [Fact]
    public void Watcher_RaisesOnlyOnChange()
    {
        var config = Config();
        string? exe = "doom.exe";
        var provider = new FakeForeground(() => exe);
        var watcher = new AutoSwitchWatcher(provider, () => config);

        var changes = new List<SwitchDecision>();
        watcher.DecisionChanged += changes.Add;

        watcher.Poll();          // doom → Activate
        watcher.Poll();          // doom again → no change
        exe = "firestorm.exe";
        watcher.Poll();          // native → Yield (change)
        exe = "firestorm.exe";
        watcher.Poll();          // same → no change

        Assert.Equal(2, changes.Count);
        Assert.Equal(SwitchMode.Activate, changes[0].Mode);
        Assert.Equal(SwitchMode.Yield, changes[1].Mode);
    }

    [Fact]
    public void Reset_ReRaises_SameDecision_OnNextPoll()
    {
        var config = Config();
        var provider = new FakeForeground(() => "doom.exe");
        var watcher = new AutoSwitchWatcher(provider, () => config);

        var changes = new List<SwitchDecision>();
        watcher.DecisionChanged += changes.Add;

        watcher.Poll();   // doom → Activate (raise)
        watcher.Poll();   // same → no raise
        watcher.Reset();  // forget last decision (e.g. after an editor session)
        watcher.Poll();   // same decision, but re-raised because of Reset

        Assert.Equal(2, changes.Count);
        Assert.All(changes, c => Assert.Equal(SwitchMode.Activate, c.Mode));
    }

    private sealed class FakeForeground(Func<string?> get) : IForegroundProcessProvider
    {
        public string? GetForegroundExecutable() => get();
    }
}
