// Copyright 2020-2026 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");

using System.Reflection;
using Shouldly;

namespace ZB.MOM.NatsNet.Server.Tests.JetStream;

/// <summary>
/// Tests for JetStream API error helpers.
/// Mirrors server/jetstream_errors_test.go.
/// </summary>
public sealed class JetStreamErrorsTests
{
    [Fact] // T:1381
    public void IsNatsErr_ShouldSucceed()
    {
        JsApiErrors.IsNatsErr(
            JsApiErrors.NotEnabledForAccount,
            JsApiErrors.NotEnabledForAccount.ErrCode).ShouldBeTrue();

        JsApiErrors.IsNatsErr(
            JsApiErrors.NotEnabledForAccount,
            JsApiErrors.ClusterNotActive.ErrCode).ShouldBeFalse();

        JsApiErrors.IsNatsErr(
            JsApiErrors.NotEnabledForAccount,
            JsApiErrors.ClusterNotActive.ErrCode,
            JsApiErrors.ClusterNotAvail.ErrCode).ShouldBeFalse();

        JsApiErrors.IsNatsErr(
            JsApiErrors.NotEnabledForAccount,
            JsApiErrors.ClusterNotActive.ErrCode,
            JsApiErrors.NotEnabledForAccount.ErrCode).ShouldBeTrue();

        JsApiErrors.IsNatsErr(
            new JsApiError { ErrCode = JsApiErrors.NotEnabledForAccount.ErrCode },
            1,
            JsApiErrors.ClusterNotActive.ErrCode,
            JsApiErrors.NotEnabledForAccount.ErrCode).ShouldBeTrue();

        JsApiErrors.IsNatsErr(
            new JsApiError { ErrCode = JsApiErrors.NotEnabledForAccount.ErrCode },
            1,
            2,
            JsApiErrors.ClusterNotActive.ErrCode).ShouldBeFalse();

        JsApiErrors.IsNatsErr(null, JsApiErrors.ClusterNotActive.ErrCode).ShouldBeFalse();
        JsApiErrors.IsNatsErr(new InvalidOperationException("x"), JsApiErrors.ClusterNotActive.ErrCode).ShouldBeFalse();
    }

    [Fact] // T:1382
    public void ApiError_Error_ShouldSucceed()
    {
        JsApiErrors.Error(JsApiErrors.ClusterNotActive).ShouldBe("JetStream not in clustered mode (10006)");
    }

    [Fact] // T:1383
    public void ApiError_NewWithTags_ShouldSucceed()
    {
        var ne = JsApiErrors.NewJSRestoreSubscribeFailedError(new Exception("failed error"), "the.subject");
        ne.Description.ShouldBe("JetStream unable to subscribe to restore snapshot the.subject: failed error");
        ReferenceEquals(ne, JsApiErrors.RestoreSubscribeFailed).ShouldBeFalse();
    }

    [Fact] // T:1384
    public void ApiError_NewWithUnless_ShouldSucceed()
    {
        var notEnabled = JsApiErrors.NotEnabledForAccount.ErrCode;
        var streamRestore = JsApiErrors.StreamRestore.ErrCode;
        var peerRemap = JsApiErrors.PeerRemap.ErrCode;

        JsApiErrors.IsNatsErr(
            JsApiErrors.NewJSStreamRestoreError(
                new Exception("failed error"),
                JsApiErrors.Unless(JsApiErrors.NotEnabledForAccount)),
            notEnabled).ShouldBeTrue();

        JsApiErrors.IsNatsErr(
            JsApiErrors.NewJSStreamRestoreError(new Exception("failed error")),
            streamRestore).ShouldBeTrue();

        JsApiErrors.IsNatsErr(
            JsApiErrors.NewJSStreamRestoreError(
                new Exception("failed error"),
                JsApiErrors.Unless(new Exception("other error"))),
            streamRestore).ShouldBeTrue();

        JsApiErrors.IsNatsErr(
            JsApiErrors.NewJSPeerRemapError(JsApiErrors.Unless(JsApiErrors.NotEnabledForAccount)),
            notEnabled).ShouldBeTrue();

        JsApiErrors.IsNatsErr(
            JsApiErrors.NewJSPeerRemapError(JsApiErrors.Unless(null)),
            peerRemap).ShouldBeTrue();

        JsApiErrors.IsNatsErr(
            JsApiErrors.NewJSPeerRemapError(JsApiErrors.Unless(new Exception("other error"))),
            peerRemap).ShouldBeTrue();
    }

    [Fact]
    public void ParseOpts_WithUnlessApiErrorOption_ReturnsOverride()
    {
        var parseOpts = typeof(JsApiErrors).GetMethod(
            "ParseOpts",
            BindingFlags.NonPublic | BindingFlags.Static);

        parseOpts.ShouldNotBeNull();

        var expected = new JsApiError { Code = 401, ErrCode = 2048, Description = "override" };
        var result = parseOpts.Invoke(
            null,
            [new JsApiErrors.ErrorOption[] { JsApiErrors.Unless(expected) }]);

        result.ShouldBeAssignableTo<JsApiError>()
            .ErrCode.ShouldBe(expected.ErrCode);
    }

    [Fact]
    public void ToReplacerArgs_WithStringErrorAndObject_ConvertsToStringValues()
    {
        var toReplacerArgs = typeof(JsApiErrors).GetMethod(
            "ToReplacerArgs",
            BindingFlags.NonPublic | BindingFlags.Static);

        toReplacerArgs.ShouldNotBeNull();

        var result = toReplacerArgs.Invoke(
            null,
            [new object?[] { "{string}", "value", "{error}", new InvalidOperationException("boom"), "{number}", 42 }]);

        result.ShouldBeAssignableTo<string[]>()
            .ShouldBe(["{string}", "value", "{error}", "boom", "{number}", "42"]);
    }
}
