using ZB.MOM.WW.Secrets.Cli.Interactive;

namespace ZB.MOM.WW.Secrets.Tests.Cli.Interactive;

/// <summary>
/// Exercises <see cref="RecentTargetsStore"/> against a real temp file, verifying round-trip
/// persistence, ordering/dedup/cap behavior, corrupt-file tolerance, and — the hard security
/// invariant — that the persisted JSON never carries anything beyond name/path/timestamp.
/// </summary>
public sealed class RecentTargetsStoreTests : IDisposable
{
    private readonly string _dir =
        Path.Combine(Path.GetTempPath(), $"zb-secrets-recent-{Guid.NewGuid():N}");

    private string FilePath => Path.Combine(_dir, "recent-targets.json");

    public void Dispose()
    {
        if (Directory.Exists(_dir))
        {
            try { Directory.Delete(_dir, recursive: true); } catch (IOException) { /* best-effort temp cleanup */ }
        }
    }

    /// <summary>A minimal, manually-advanceable <see cref="TimeProvider"/> fake for deterministic ordering assertions.</summary>
    private sealed class FakeTimeProvider : TimeProvider
    {
        private DateTimeOffset _now;

        public FakeTimeProvider(DateTimeOffset start) => _now = start;

        public void Advance(TimeSpan by) => _now += by;

        public override DateTimeOffset GetUtcNow() => _now;
    }

    [Fact]
    public void Load_returns_empty_when_file_missing()
    {
        var sut = new RecentTargetsStore(FilePath);

        var result = sut.Load();

        Assert.Empty(result);
    }

    [Fact]
    public void Touch_then_load_roundtrips_name_and_path()
    {
        var sut = new RecentTargetsStore(FilePath);

        sut.Touch("OtOpcUa", "/apps/otopcua/appsettings.json");
        var result = sut.Load();

        var entry = Assert.Single(result);
        Assert.Equal("OtOpcUa", entry.Name);
        Assert.Equal("/apps/otopcua/appsettings.json", entry.AppSettingsPath);
    }

    [Fact]
    public void Touch_existing_path_moves_it_first_and_updates_stamp()
    {
        var time = new FakeTimeProvider(new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero));
        var sut = new RecentTargetsStore(FilePath, time);

        sut.Touch("OtOpcUa", "/apps/otopcua/appsettings.json");
        time.Advance(TimeSpan.FromMinutes(1));
        sut.Touch("ScadaBridge", "/apps/scadabridge/appsettings.json");
        time.Advance(TimeSpan.FromMinutes(1));
        var beforeRetouch = time.GetUtcNow();
        sut.Touch("OtOpcUa", "/apps/otopcua/appsettings.json");

        var result = sut.Load();

        Assert.Equal(2, result.Count);
        Assert.Equal("OtOpcUa", result[0].Name);
        Assert.Equal("/apps/otopcua/appsettings.json", result[0].AppSettingsPath);
        Assert.Equal(beforeRetouch, result[0].LastUsedUtc);
        Assert.Equal("ScadaBridge", result[1].Name);
    }

    [Fact]
    public void Load_tolerates_corrupt_json_by_returning_empty()
    {
        Directory.CreateDirectory(_dir);
        File.WriteAllText(FilePath, "{ this is not valid json ][");
        var sut = new RecentTargetsStore(FilePath);

        var result = sut.Load();

        Assert.Empty(result);
    }

    [Fact]
    public void Touch_caps_list_at_ten_entries()
    {
        var sut = new RecentTargetsStore(FilePath);

        for (var i = 0; i < 12; i++)
        {
            sut.Touch($"App{i}", $"/apps/app{i}/appsettings.json");
        }

        var result = sut.Load();

        Assert.Equal(10, result.Count);
        Assert.Equal("App11", result[0].Name);
        Assert.Equal("App2", result[9].Name);
    }

    [Fact]
    public void Saved_json_contains_no_key_material_fields()
    {
        var sut = new RecentTargetsStore(FilePath);

        sut.Touch("OtOpcUa", "/apps/otopcua/appsettings.json");
        var json = File.ReadAllText(FilePath);

        Assert.Contains("name", json, StringComparison.Ordinal);
        Assert.Contains("appSettingsPath", json, StringComparison.Ordinal);
        Assert.Contains("lastUsedUtc", json, StringComparison.Ordinal);
        Assert.DoesNotContain("key", json, StringComparison.OrdinalIgnoreCase);
        Assert.DoesNotContain("kek", json, StringComparison.OrdinalIgnoreCase);
        Assert.DoesNotContain("secret", json, StringComparison.OrdinalIgnoreCase);
        Assert.DoesNotContain("password", json, StringComparison.OrdinalIgnoreCase);
    }
}
