using Google.Protobuf;
using ZB.MOM.WW.MxGateway.Client;
using ZB.MOM.WW.MxGateway.Contracts.Proto;

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

public sealed class MxCommandReplyExtensionsTests
{
    /// <summary>Verifies that successful replies pass both protocol and MxAccess success checks.</summary>
    [Fact]
    public void EnsureSuccess_WithRegisterFixture_ReturnsReply()
    {
        MxCommandReply reply = ReadReplyFixture("register.ok.reply.json");

        Assert.Same(reply, reply.EnsureProtocolSuccess());
        Assert.Same(reply, reply.EnsureMxAccessSuccess());
    }

    /// <summary>Verifies that MxAccess failures throw with preserved HResult and status details.</summary>
    [Fact]
    public void EnsureMxAccessSuccess_WithFailureFixture_PreservesHResultAndStatuses()
    {
        MxCommandReply reply = ReadReplyFixture("write.mxaccess-failure.reply.json");

        reply.EnsureProtocolSuccess();
        MxAccessException exception = Assert.Throws<MxAccessException>(
            reply.EnsureMxAccessSuccess);

        Assert.Equal(-2147220992, exception.HResultCode);
        Assert.Equal(reply.Statuses.Count, exception.Statuses.Count);
        Assert.Equal(reply, exception.Reply);
        Assert.Contains("0x80040200", exception.Message);
    }

    /// <summary>Verifies that a non-OK status category fails even when the raw success member is set.</summary>
    [Fact]
    public void EnsureMxAccessSuccess_WithNonOkCategoryAndSuccessSet_Throws()
    {
        MxCommandReply reply = ReadReplyFixture(
            "write.status-category-error-success-set.reply.json");

        reply.EnsureProtocolSuccess();
        MxAccessException exception = Assert.Throws<MxAccessException>(
            reply.EnsureMxAccessSuccess);

        Assert.Equal(1, Assert.Single(exception.Statuses).Success);
        Assert.Contains("CommunicationError", exception.Message, StringComparison.Ordinal);
    }

    /// <summary>Verifies that an Ok status category succeeds even when the raw success member is zero.</summary>
    [Fact]
    public void EnsureMxAccessSuccess_WithOkCategoryAndZeroSuccess_ReturnsReply()
    {
        MxCommandReply reply = ReadReplyFixture(
            "write.status-category-ok-success-zero.reply.json");

        Assert.Equal(0, Assert.Single(reply.Statuses).Success);
        Assert.Same(reply, reply.EnsureProtocolSuccess());
        Assert.Same(reply, reply.EnsureMxAccessSuccess());
    }

    /// <summary>Verifies that a positive HResult (S_FALSE) is a COM success code, not a failure.</summary>
    [Fact]
    public void EnsureMxAccessSuccess_WithPositiveHResult_ReturnsReply()
    {
        MxCommandReply reply = ReadReplyFixture("write.hresult-s-false.reply.json");

        Assert.Equal(1, reply.Hresult);
        Assert.Same(reply, reply.EnsureProtocolSuccess());
        Assert.Same(reply, reply.EnsureMxAccessSuccess());
    }

    /// <summary>Verifies that a negative HResult fails even when every status entry is Ok.</summary>
    [Fact]
    public void EnsureMxAccessSuccess_WithNegativeHResult_Throws()
    {
        MxCommandReply reply = ReadReplyFixture("write.hresult-e-fail.reply.json");

        reply.EnsureProtocolSuccess();
        MxAccessException exception = Assert.Throws<MxAccessException>(
            reply.EnsureMxAccessSuccess);

        Assert.Equal(-2147467259, exception.HResultCode);
    }

    /// <summary>Verifies that session-not-found protocol failures throw the correct gateway exception.</summary>
    [Fact]
    public void EnsureProtocolSuccess_WithSessionFailure_ThrowsSessionException()
    {
        MxCommandReply reply = new()
        {
            SessionId = "session-missing",
            CorrelationId = "correlation",
            ProtocolStatus = new ProtocolStatus
            {
                Code = ProtocolStatusCode.SessionNotFound,
                Message = "Session was not found.",
            },
        };

        MxGatewaySessionException exception = Assert.Throws<MxGatewaySessionException>(
            reply.EnsureProtocolSuccess);

        Assert.Equal("session-missing", exception.SessionId);
        Assert.Equal(ProtocolStatusCode.SessionNotFound, exception.ProtocolStatus?.Code);
    }

    private static MxCommandReply ReadReplyFixture(string fileName)
    {
        DirectoryInfo directory = new(AppContext.BaseDirectory);
        while (directory is not null)
        {
            string path = Path.Combine(
                directory.FullName,
                "clients",
                "proto",
                "fixtures",
                "behavior",
                "command-replies",
                fileName);

            if (File.Exists(path))
            {
                return JsonParser.Default.Parse<MxCommandReply>(File.ReadAllText(path));
            }

            directory = directory.Parent!;
        }

        throw new FileNotFoundException(fileName);
    }
}
