// Copyright 2012-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Adapted from server/errors.go in the NATS server Go source.
// Note: errorsUnwrap() and ErrorIs() are Go 1.12 compat shims — .NET has
// Exception.InnerException and errors.Is() equivalents built in;
// both are represented here for completeness but map to built-in .NET patterns.

namespace ZB.MOM.NatsNet.Server;

// -------------------------------------------------------------------------
// Sentinel error values (mirrors package-level var block in errors.go)
// -------------------------------------------------------------------------

/// <summary>
/// Standard NATS server error sentinels.
/// Mirrors the package-level <c>var</c> error block in server/errors.go.
/// </summary>
public static class ServerErrors
{
    public static readonly Exception ErrConnectionClosed =
        new InvalidOperationException("connection closed");

    public static readonly Exception ErrAuthentication =
        new InvalidOperationException("authentication error");

    // Alias used by ClientConnection.AuthViolation(); mirrors Go's ErrAuthorization.
    public static readonly Exception ErrAuthorization =
        new InvalidOperationException("Authorization Violation");

    public static readonly Exception ErrAuthTimeout =
        new InvalidOperationException("authentication timeout");

    public static readonly Exception ErrAuthExpired =
        new InvalidOperationException("authentication expired");

    public static readonly Exception ErrAuthProxyNotTrusted =
        new InvalidOperationException("proxy is not trusted");

    public static readonly Exception ErrAuthProxyRequired =
        new InvalidOperationException("proxy connection required");

    public static readonly Exception ErrMaxPayload =
        new InvalidOperationException("maximum payload exceeded");

    public static readonly Exception ErrMaxControlLine =
        new InvalidOperationException("maximum control line exceeded");

    public static readonly Exception ErrReservedPublishSubject =
        new InvalidOperationException("reserved internal subject");

    public static readonly Exception ErrBadPublishSubject =
        new InvalidOperationException("invalid publish subject");

    public static readonly Exception ErrBadSubject =
        new InvalidOperationException("invalid subject");

    public static readonly Exception ErrBadQualifier =
        new InvalidOperationException("bad qualifier");

    public static readonly Exception ErrBadClientProtocol =
        new InvalidOperationException("invalid client protocol");

    public static readonly Exception ErrTooManyConnections =
        new InvalidOperationException("maximum connections exceeded");

    public static readonly Exception ErrTooManyAccountConnections =
        new InvalidOperationException("maximum account active connections exceeded");

    public static readonly Exception ErrLeafNodeLoop =
        new InvalidOperationException("leafnode loop detected");

    public static readonly Exception ErrTooManySubs =
        new InvalidOperationException("maximum subscriptions exceeded");

    public static readonly Exception ErrTooManySubTokens =
        new InvalidOperationException("subject has exceeded number of tokens limit");

    public static readonly Exception ErrClientConnectedToRoutePort =
        new InvalidOperationException("attempted to connect to route port");

    public static readonly Exception ErrClientConnectedToLeafNodePort =
        new InvalidOperationException("attempted to connect to leaf node port");

    public static readonly Exception ErrLeafNodeHasSameClusterName =
        new InvalidOperationException("remote leafnode has same cluster name");

    public static readonly Exception ErrLeafNodeDisabled =
        new InvalidOperationException("leafnodes disabled");

    public static readonly Exception ErrConnectedToWrongPort =
        new InvalidOperationException("attempted to connect to wrong port");

    public static readonly Exception ErrAccountExists =
        new InvalidOperationException("account exists");

    public static readonly Exception ErrBadAccount =
        new InvalidOperationException("bad account");

    public static readonly Exception ErrReservedAccount =
        new InvalidOperationException("reserved account");

    public static readonly Exception ErrMissingAccount =
        new InvalidOperationException("account missing");

    public static readonly Exception ErrMissingService =
        new InvalidOperationException("service missing");

    public static readonly Exception ErrBadServiceType =
        new InvalidOperationException("bad service response type");

    public static readonly Exception ErrBadSampling =
        new InvalidOperationException("bad sampling percentage, should be 1-100");

    public static readonly Exception ErrAccountValidation =
        new InvalidOperationException("account validation failed");

    public static readonly Exception ErrAccountExpired =
        new InvalidOperationException("account expired");

    public static readonly Exception ErrNoAccountResolver =
        new InvalidOperationException("account resolver missing");

    public static readonly Exception ErrAccountResolverUpdateTooSoon =
        new InvalidOperationException("account resolver update too soon");

    public static readonly Exception ErrAccountResolverSameClaims =
        new InvalidOperationException("account resolver no new claims");

    public static readonly Exception ErrStreamImportAuthorization =
        new InvalidOperationException("stream import not authorized");

    public static readonly Exception ErrStreamImportBadPrefix =
        new InvalidOperationException("stream import prefix can not contain wildcard tokens");

    public static readonly Exception ErrStreamImportDuplicate =
        new InvalidOperationException("stream import already exists");

    public static readonly Exception ErrServiceImportAuthorization =
        new InvalidOperationException("service import not authorized");

    public static readonly Exception ErrImportFormsCycle =
        new InvalidOperationException("import forms a cycle");

    public static readonly Exception ErrCycleSearchDepth =
        new InvalidOperationException("search cycle depth exhausted");

    public static readonly Exception ErrClientOrRouteConnectedToGatewayPort =
        new InvalidOperationException("attempted to connect to gateway port");

    public static readonly Exception ErrWrongGateway =
        new InvalidOperationException("wrong gateway");

    public static readonly Exception ErrGatewayNameHasSpaces =
        new InvalidOperationException("gateway name cannot contain spaces");

    public static readonly Exception ErrNoSysAccount =
        new InvalidOperationException("system account not setup");

    public static readonly Exception ErrRevocation =
        new InvalidOperationException("credentials have been revoked");

    public static readonly Exception ErrServerNotRunning =
        new InvalidOperationException("server is not running");

    public static readonly Exception ErrServerNameHasSpaces =
        new InvalidOperationException("server name cannot contain spaces");

    public static readonly Exception ErrBadMsgHeader =
        new InvalidOperationException("bad message header detected");

    public static readonly Exception ErrMsgHeadersNotSupported =
        new InvalidOperationException("message headers not supported");

    public static readonly Exception ErrNoRespondersRequiresHeaders =
        new InvalidOperationException("no responders requires headers support");

    public static readonly Exception ErrClusterNameConfigConflict =
        new InvalidOperationException("cluster name conflicts between cluster and gateway definitions");

    public static readonly Exception ErrClusterNameRemoteConflict =
        new InvalidOperationException("cluster name from remote server conflicts");

    public static readonly Exception ErrClusterNameHasSpaces =
        new InvalidOperationException("cluster name cannot contain spaces");

    public static readonly Exception ErrMalformedSubject =
        new InvalidOperationException("malformed subject");

    public static readonly Exception ErrSubscribePermissionViolation =
        new InvalidOperationException("subscribe permission violation");

    public static readonly Exception ErrNoTransforms =
        new InvalidOperationException("no matching transforms available");

    public static readonly Exception ErrCertNotPinned =
        new InvalidOperationException("certificate not pinned");

    public static readonly Exception ErrDuplicateServerName =
        new InvalidOperationException("duplicate server name");

    public static readonly Exception ErrMinimumVersionRequired =
        new InvalidOperationException("minimum version required");

    // Mapping destination errors — the Go source wraps ErrInvalidMappingDestination.
    // In .NET we use a common base message and chain inner exceptions where needed.
    public static readonly Exception ErrInvalidMappingDestination =
        new InvalidOperationException("invalid mapping destination");

    public static readonly Exception ErrInvalidMappingDestinationSubject =
        new InvalidOperationException("invalid mapping destination: invalid transform");

    public static readonly Exception ErrMappingDestinationNotUsingAllWildcards =
        new InvalidOperationException("invalid mapping destination: not using all of the token wildcard(s)");

    public static readonly Exception ErrUnknownMappingDestinationFunction =
        new InvalidOperationException("invalid mapping destination: unknown function");

    public static readonly Exception ErrMappingDestinationIndexOutOfRange =
        new InvalidOperationException("invalid mapping destination: wildcard index out of range");

    public static readonly Exception ErrMappingDestinationNotEnoughArgs =
        new InvalidOperationException("invalid mapping destination: not enough arguments passed to the function");

    public static readonly Exception ErrMappingDestinationInvalidArg =
        new InvalidOperationException("invalid mapping destination: function argument is invalid or in the wrong format");

    public static readonly Exception ErrMappingDestinationTooManyArgs =
        new InvalidOperationException("invalid mapping destination: too many arguments passed to the function");

    public static readonly Exception ErrMappingDestinationNotSupportedForImport =
        new InvalidOperationException("invalid mapping destination: the only mapping function allowed for import transforms is {{Wildcard()}}");
}

// -------------------------------------------------------------------------
// mappingDestinationErr (mirrors server/errors.go)
// -------------------------------------------------------------------------

/// <summary>
/// An error type for subject mapping destination validation failures.
/// Mirrors <c>mappingDestinationErr</c> in server/errors.go.
/// </summary>
public sealed class MappingDestinationException : Exception
{
    private readonly string _token;

    public MappingDestinationException(string token, Exception inner)
        : base($"{inner.Message} in {token}", inner)
    {
        _token = token;
    }

    // Is() in Go is implemented via IsInvalidMappingDestination below.

    /// <summary>
    /// Returns true when compared against <see cref="ServerErrors.ErrInvalidMappingDestination"/>.
    /// Mirrors <c>mappingDestinationErr.Is</c>.
    /// </summary>
    public bool Is(Exception target) =>
        ReferenceEquals(target, ServerErrors.ErrInvalidMappingDestination);
}

// -------------------------------------------------------------------------
// configErr / unknownConfigFieldErr / configWarningErr (mirrors server/errors.go)
// -------------------------------------------------------------------------
// Note: these types depend on a config-file token interface defined in the
// configuration parser (ported in session 03). Forward-declared here with the
// minimal interface needed for error formatting.

/// <summary>
/// Represents a source location within a configuration file.
/// Mirrors the <c>token</c> interface used by configErr in server/errors.go.
/// Full implementation is provided by the configuration parser (session 03).
/// </summary>
public interface IConfigToken
{
    string SourceFile();
    int Line();
    int Position();
}

/// <summary>
/// A configuration parsing error with source location.
/// Mirrors <c>configErr</c> in server/errors.go.
/// </summary>
public class ConfigException : Exception
{
    private readonly IConfigToken? _token;

    public ConfigException(IConfigToken? token, string reason)
        : base(reason)
    {
        _token = token;
    }

    /// <summary>Returns "file:line:col" source location, or empty string if no token.</summary>
    public new string Source() =>
        _token != null
            ? $"{_token.SourceFile()}:{_token.Line()}:{_token.Position()}"
            : string.Empty;

    public override string Message =>
        _token != null ? $"{Source()}: {base.Message}" : base.Message;
}

/// <summary>
/// A configuration error for an unknown field (pedantic mode).
/// Mirrors <c>unknownConfigFieldErr</c> in server/errors.go.
/// </summary>
public sealed class UnknownConfigFieldException : ConfigException
{
    private readonly string _field;

    public UnknownConfigFieldException(IConfigToken token, string field)
        : base(token, $"unknown field \"{field}\"")
    {
        _field = field;
    }
}

/// <summary>
/// A configuration warning for invalid field usage (pedantic mode).
/// Mirrors <c>configWarningErr</c> in server/errors.go.
/// </summary>
public sealed class ConfigWarningException : ConfigException
{
    private readonly string _field;

    public ConfigWarningException(IConfigToken token, string field, string reason)
        : base(token, $"invalid use of field \"{field}\": {reason}")
    {
        _field = field;
    }
}

/// <summary>
/// Aggregates configuration warnings and hard errors from parsing.
/// Mirrors <c>processConfigErr</c> in server/errors.go.
/// </summary>
public sealed class ProcessConfigException : Exception
{
    public IReadOnlyList<Exception> Warnings { get; }
    public IReadOnlyList<Exception> Errors { get; }

    public ProcessConfigException(IReadOnlyList<Exception> errors, IReadOnlyList<Exception> warnings)
        : base(BuildMessage(errors, warnings))
    {
        Errors = errors;
        Warnings = warnings;
    }

    private static string BuildMessage(IReadOnlyList<Exception> errors, IReadOnlyList<Exception> warnings)
    {
        var sb = new System.Text.StringBuilder();
        foreach (var w in warnings) sb.AppendLine(w.Message);
        foreach (var e in errors) sb.AppendLine(e.Message);
        return sb.ToString();
    }
}

// -------------------------------------------------------------------------
// errCtx — error with attached tracing context (mirrors server/errors.go)
// -------------------------------------------------------------------------

/// <summary>
/// Wraps an exception and attaches additional tracing context.
/// Context is not included in <see cref="Exception.Message"/> but is
/// accessible via <see cref="Context"/> and <see cref="UnpackIfErrorCtx"/>.
/// Mirrors <c>errCtx</c> in server/errors.go.
/// </summary>
public sealed class ErrorCtx : Exception
{
    public string Ctx { get; }

    public ErrorCtx(Exception inner, string ctx)
        : base(inner.Message, inner)
    {
        Ctx = ctx;
    }

    /// <summary>
    /// Returns the context string attached to this error.
    /// Mirrors <c>errCtx.Context()</c>.
    /// </summary>
    public string Context() => Ctx;
}

/// <summary>
/// Factory and utilities for <see cref="ErrorCtx"/>.
/// Mirrors <c>NewErrorCtx</c>, <c>UnpackIfErrorCtx</c>, and <c>ErrorIs</c> in server/errors.go.
/// </summary>
public static class ErrorContextHelper
{
    /// <summary>
    /// Creates an <see cref="ErrorCtx"/> wrapping <paramref name="err"/> with formatted context.
    /// Mirrors <c>NewErrorCtx</c>.
    /// </summary>
    public static Exception NewErrorCtx(Exception err, string format, params object[] args) =>
        new ErrorCtx(err, string.Format(format, args));

    /// <summary>
    /// If <paramref name="err"/> is an <see cref="ErrorCtx"/>, returns
    /// "original.Message: ctx" (recursively). Otherwise returns err.Message.
    /// Mirrors <c>UnpackIfErrorCtx</c>.
    /// </summary>
    public static string UnpackIfErrorCtx(Exception err)
    {
        if (err is ErrorCtx ectx)
        {
            if (ectx.InnerException is ErrorCtx)
                return $"{UnpackIfErrorCtx(ectx.InnerException!)}: {ectx.Ctx}";
            return $"{ectx.InnerException!.Message}: {ectx.Ctx}";
        }
        return err.Message;
    }

    /// <summary>
    /// Walks the inner exception chain checking reference equality and
    /// <see cref="MappingDestinationException.Is"/> overrides.
    /// In .NET, prefer <c>errors.Is</c>-equivalent patterns; this mirrors
    /// the Go <c>ErrorIs</c> shim exactly.
    /// Mirrors <c>ErrorIs</c>.
    /// </summary>
    public static bool ErrorIs(Exception? err, Exception? target)
    {
        if (err == null || target == null)
            return ReferenceEquals(err, target);

        var current = err;
        while (current != null)
        {
            if (ReferenceEquals(current, target))
                return true;
            if (current is MappingDestinationException mde && mde.Is(target))
                return true;
            current = current.InnerException;
        }
        return false;
    }
}
