using FluentAssertions;
using DragonISO.App.Services;

namespace DragonISO.App.Tests.Services;

// Unit tests for ThemeManager â€” exercise the resolve / set / toggle
// state machine behind the test-only constructor that takes stub seams
// instead of touching HKCU and %LOCALAPPDATA%. Apply() and the
// SystemEvents subscription are intentionally NOT exercised here:
// they require Application.Current and a real WPF dispatcher, both of
// which would couple these tests to the host runtime.
public sealed class ThemeManagerTests
{
    private static ThemeManager NewManager(
        bool systemDark = true,
        string? initialPreference = null,
        Action<string>? captureSave = null) =>
        new ThemeManager(
            isSystemDark: () => systemDark,
            loadPreference: () => initialPreference,
            savePreference: captureSave ?? (_ => { }),
            subscribeToSystemPreference: false);

    [Fact]
    public void Set_DarkThenLight_RoundTripsPreferenceAndResolution()
    {
        var saves = new List<string>();
        var tm = NewManager(systemDark: false, captureSave: saves.Add);

        tm.Set("Dark");
        tm.Preference.Should().Be("Dark");
        tm.ResolveTheme().Should().Be("Dark");

        tm.Set("Light");
        tm.Preference.Should().Be("Light");
        tm.ResolveTheme().Should().Be("Light");

        saves.Should().Equal("Dark", "Light");
    }

    [Theory]
    [InlineData(true, "Dark")]
    [InlineData(false, "Light")]
    public void ResolveTheme_FollowsSystem_WhenPreferenceIsSystem(bool isSystemDark, string expected)
    {
        var tm = NewManager(systemDark: isSystemDark, initialPreference: "System");
        tm.Preference.Should().Be("System");
        tm.ResolveTheme().Should().Be(expected);
    }

    [Fact]
    public void Toggle_FromSystemDark_PinsToOppositeOfCurrent()
    {
        // System currently resolves to Dark â†’ toggle should flip
        // *preference* to Light (the opposite of the currently-displayed
        // theme), not back to System. The point of the click is a
        // visible change.
        var tm = NewManager(systemDark: true, initialPreference: "System");

        tm.Toggle();

        tm.Preference.Should().Be("Light");
        tm.ResolveTheme().Should().Be("Light");
    }

    [Fact]
    public void Toggle_FromSystemLight_PinsToOppositeOfCurrent()
    {
        var tm = NewManager(systemDark: false, initialPreference: "System");

        tm.Toggle();

        tm.Preference.Should().Be("Dark");
        tm.ResolveTheme().Should().Be("Dark");
    }

    [Fact]
    public void Toggle_FromDark_FlipsToLight()
    {
        var tm = NewManager(initialPreference: "Dark");

        tm.Toggle();

        tm.Preference.Should().Be("Light");
    }

    [Fact]
    public void Toggle_FromLight_FlipsToDark()
    {
        var tm = NewManager(initialPreference: "Light");

        tm.Toggle();

        tm.Preference.Should().Be("Dark");
    }

    [Theory]
    [InlineData("invalid")]
    [InlineData("dark")]      // case-sensitive â€” we accept exactly Dark
    [InlineData("LIGHT")]
    [InlineData("")]
    public void Set_RejectsInvalidPreferenceWithArgumentException(string bad)
    {
        var tm = NewManager();

        var act = () => tm.Set(bad);

        act.Should().Throw<ArgumentException>()
            .WithParameterName("preference");
    }

    [Fact]
    public void Constructor_DefaultsToSystem_WhenLoadReturnsNull()
    {
        // Simulates a fresh install / corrupt prefs file: loadPreference
        // returns null; the manager falls back to the in-memory default
        // of "System" rather than throwing.
        var tm = NewManager(initialPreference: null);

        tm.Preference.Should().Be("System");
    }

    [Fact]
    public void Constructor_DefaultsToSystem_WhenLoadReturnsInvalidValue()
    {
        // A prefs file written by a future version with an unknown
        // value mustn't poison the in-memory state â€” invalid loads
        // fall back to the default, same as a missing file.
        var tm = NewManager(initialPreference: "Rainbow");

        tm.Preference.Should().Be("System");
    }

    [Fact]
    public void Constructor_HonoursPersistedPreference()
    {
        var tm = NewManager(initialPreference: "Dark");

        tm.Preference.Should().Be("Dark");
    }

    [Fact]
    public void Constructor_SurvivesLoadException()
    {
        // The production singleton hits disk via UIPreferences.Load; a
        // disk fault must NOT escape the ctor or the app loses theming
        // entirely. Verify the swallow.
        var tm = new ThemeManager(
            isSystemDark: () => true,
            loadPreference: () => throw new InvalidOperationException("disk faulted"),
            savePreference: _ => { },
            subscribeToSystemPreference: false);

        tm.Preference.Should().Be("System");
    }
}
