using Microsoft.Extensions.Options;
using ZB.MOM.WW.MxGateway.Server.Galaxy;

namespace ZB.MOM.WW.MxGateway.Tests.Galaxy;

/// <summary>
///     Covers <see cref="GalaxyHierarchySnapshotStore"/>: the on-disk persistence
///     that lets the Galaxy browse cache survive a cold start while the Galaxy
///     database is unreachable.
/// </summary>
public sealed class GalaxyHierarchySnapshotStoreTests : IDisposable
{
    private readonly List<string> _tempPaths = [];

    [Fact]
    public async Task SaveAsync_ThenTryLoadAsync_RoundTripsRows()
    {
        string path = CreateTempPath();
        GalaxyHierarchySnapshotStore store = CreateStore(path);
        GalaxyHierarchySnapshot snapshot = SampleSnapshot();

        await store.SaveAsync(snapshot, CancellationToken.None);
        GalaxyHierarchySnapshot? loaded = await store.TryLoadAsync(CancellationToken.None);

        Assert.NotNull(loaded);
        Assert.Equal(snapshot.LastDeployTime, loaded.LastDeployTime);
        Assert.Equal(snapshot.SavedAt, loaded.SavedAt);

        GalaxyHierarchyRow row = Assert.Single(loaded.Hierarchy);
        Assert.Equal(7, row.GobjectId);
        Assert.Equal("Pump_001", row.TagName);
        Assert.Equal(["AppPump", "Pump"], row.TemplateChain);

        Assert.Equal(2, loaded.Attributes.Count);
        GalaxyAttributeRow withDimension = loaded.Attributes[0];
        Assert.Equal("PV", withDimension.AttributeName);
        Assert.Equal(8, withDimension.ArrayDimension);
        Assert.True(withDimension.IsAlarm);
        Assert.Null(loaded.Attributes[1].ArrayDimension);
    }

    [Fact]
    public async Task TryLoadAsync_WhenNoFileExists_ReturnsNull()
    {
        GalaxyHierarchySnapshotStore store = CreateStore(CreateTempPath());

        Assert.Null(await store.TryLoadAsync(CancellationToken.None));
    }

    [Fact]
    public async Task SaveAsync_WhenPersistenceDisabled_WritesNothing()
    {
        string path = CreateTempPath();
        GalaxyHierarchySnapshotStore store = CreateStore(path, persist: false);

        await store.SaveAsync(SampleSnapshot(), CancellationToken.None);

        Assert.False(File.Exists(path));
        Assert.Null(await store.TryLoadAsync(CancellationToken.None));
    }

    [Fact]
    public async Task TryLoadAsync_WhenFileIsCorruptJson_ReturnsNull()
    {
        string path = CreateTempPath();
        await File.WriteAllTextAsync(path, "{ this is not valid json");
        GalaxyHierarchySnapshotStore store = CreateStore(path);

        Assert.Null(await store.TryLoadAsync(CancellationToken.None));
    }

    [Fact]
    public async Task TryLoadAsync_WhenSchemaVersionUnrecognized_ReturnsNull()
    {
        string path = CreateTempPath();
        await File.WriteAllTextAsync(path, """{"SchemaVersion":999,"Snapshot":null}""");
        GalaxyHierarchySnapshotStore store = CreateStore(path);

        Assert.Null(await store.TryLoadAsync(CancellationToken.None));
    }

    [Fact]
    public async Task SaveAsync_OverwritesAnEarlierSnapshot()
    {
        string path = CreateTempPath();
        GalaxyHierarchySnapshotStore store = CreateStore(path);

        await store.SaveAsync(SampleSnapshot(), CancellationToken.None);
        GalaxyHierarchySnapshot second = SampleSnapshot() with
        {
            Hierarchy = [],
            Attributes = [],
        };
        await store.SaveAsync(second, CancellationToken.None);

        GalaxyHierarchySnapshot? loaded = await store.TryLoadAsync(CancellationToken.None);
        Assert.NotNull(loaded);
        Assert.Empty(loaded.Hierarchy);
        Assert.Empty(loaded.Attributes);
    }

    private static GalaxyHierarchySnapshot SampleSnapshot() => new(
        LastDeployTime: new DateTimeOffset(2026, 5, 20, 9, 30, 0, TimeSpan.Zero),
        SavedAt: new DateTimeOffset(2026, 5, 20, 9, 31, 0, TimeSpan.Zero),
        Hierarchy:
        [
            new GalaxyHierarchyRow
            {
                GobjectId = 7,
                TagName = "Pump_001",
                ContainedName = "Pump",
                BrowseName = "Pump",
                CategoryId = 10,
                TemplateChain = ["AppPump", "Pump"],
            },
        ],
        Attributes:
        [
            new GalaxyAttributeRow
            {
                GobjectId = 7,
                TagName = "Pump_001",
                AttributeName = "PV",
                FullTagReference = "Pump_001.PV[]",
                MxDataType = 5,
                DataTypeName = "Float",
                IsArray = true,
                ArrayDimension = 8,
                IsAlarm = true,
            },
            new GalaxyAttributeRow
            {
                GobjectId = 7,
                TagName = "Pump_001",
                AttributeName = "Mode",
                FullTagReference = "Pump_001.Mode",
                MxDataType = 3,
                DataTypeName = "Integer",
                ArrayDimension = null,
            },
        ]);

    private static GalaxyHierarchySnapshotStore CreateStore(string path, bool persist = true)
    {
        GalaxyRepositoryOptions options = new()
        {
            PersistSnapshot = persist,
            SnapshotCachePath = path,
        };
        return new GalaxyHierarchySnapshotStore(Options.Create(options));
    }

    private string CreateTempPath()
    {
        string path = Path.Combine(
            Path.GetTempPath(),
            $"mxgw-galaxy-snapshot-{Guid.NewGuid():N}.json");
        _tempPaths.Add(path);
        return path;
    }

    public void Dispose()
    {
        foreach (string path in _tempPaths)
        {
            try
            {
                File.Delete(path);
                File.Delete(path + ".tmp");
            }
            catch (IOException)
            {
                // Best-effort cleanup of test scratch files.
            }
        }
    }
}
