using System.IO.MemoryMappedFiles;
using System.Text;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Supervisor;

namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests;

/// <summary>
///     The Proxy-side <see cref="PostMortemReader"/> must read the Host's MMF format
///     (magic 'OFPC', 256-byte entries). This test writes a hand-crafted file that mimics
///     the Host's layout exactly + asserts the reader decodes it correctly. Keeps the two
///     codebases in lockstep on the wire format without needing to reference the net48
///     Host assembly from the net10 test project.
/// </summary>
[Trait("Category", "Unit")]
public sealed class PostMortemReaderCompatibilityTests : IDisposable
{
    private readonly string _tempPath = Path.Combine(Path.GetTempPath(), $"focas-mmf-compat-{Guid.NewGuid():N}.bin");

    public void Dispose()
    {
        if (File.Exists(_tempPath)) File.Delete(_tempPath);
    }

    [Fact]
    public void Reader_parses_host_format_and_returns_entries_in_oldest_first_order()
    {
        const int magic = 0x4F465043;
        const int capacity = 5;
        const int headerBytes = 16;
        const int entryBytes = 256;
        const int messageOffset = 16;
        var fileBytes = headerBytes + capacity * entryBytes;

        using (var fs = new FileStream(_tempPath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read))
        {
            fs.SetLength(fileBytes);
            using var mmf = MemoryMappedFile.CreateFromFile(fs, null, fileBytes,
                MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, leaveOpen: false);
            using var acc = mmf.CreateViewAccessor(0, fileBytes, MemoryMappedFileAccess.ReadWrite);
            acc.Write(0, magic);
            acc.Write(4, 1);
            acc.Write(8, capacity);
            acc.Write(12, 2); // writeIndex — next write would land at slot 2

            void WriteEntry(int slot, long ts, long op, string msg)
            {
                var offset = headerBytes + slot * entryBytes;
                acc.Write(offset + 0, ts);
                acc.Write(offset + 8, op);
                var bytes = Encoding.UTF8.GetBytes(msg);
                acc.WriteArray(offset + messageOffset, bytes, 0, bytes.Length);
                acc.Write(offset + messageOffset + bytes.Length, (byte)0);
            }

            WriteEntry(0, 100, 1, "op-a");
            WriteEntry(1, 200, 2, "op-b");
            // Slots 2,3 unwritten (ts=0) — reader must skip.
            WriteEntry(4, 50, 9, "old-wrapped");
        }

        var entries = new PostMortemReader(_tempPath).ReadAll();
        entries.Length.ShouldBe(3);
        // writeIndex=2 means the ring walk starts at slot 2, so iteration order is 2→3→4→0→1.
        // Slots 2 and 3 are empty; 4 yields "old-wrapped"; then 0="op-a", 1="op-b".
        entries[0].Message.ShouldBe("old-wrapped");
        entries[1].Message.ShouldBe("op-a");
        entries[2].Message.ShouldBe("op-b");
    }

    [Fact]
    public void Reader_returns_empty_when_file_missing()
    {
        new PostMortemReader(_tempPath + "-does-not-exist").ReadAll().ShouldBeEmpty();
    }

    [Fact]
    public void Reader_returns_empty_when_magic_mismatches()
    {
        File.WriteAllBytes(_tempPath, new byte[1024]);
        new PostMortemReader(_tempPath).ReadAll().ShouldBeEmpty();
    }
}
