// 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/opts.go in the NATS server Go source.

using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Configuration;

namespace ZB.MOM.NatsNet.Server.Config;

/// <summary>
/// Loads and binds NATS server configuration from an <c>appsettings.json</c> file.
/// Replaces the Go <c>processConfigFile</c> / <c>processConfigFileLine</c> pipeline
/// and all <c>parse*</c> helper functions in server/opts.go.
/// </summary>
public static class ServerOptionsConfiguration
{
    /// <summary>
    /// Creates a <see cref="JsonSerializerOptions"/> instance pre-configured with all
    /// NATS-specific JSON converters.
    /// </summary>
    public static JsonSerializerOptions CreateJsonOptions()
    {
        var opts = new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true,
            AllowTrailingCommas = true,
            ReadCommentHandling = JsonCommentHandling.Skip,
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
        };
        opts.Converters.Add(new NatsDurationJsonConverter());
        opts.Converters.Add(new TlsVersionJsonConverter());
        opts.Converters.Add(new StorageSizeJsonConverter());
        return opts;
    }

    /// <summary>
    /// Reads a JSON file at <paramref name="path"/> and returns a bound
    /// <see cref="ServerOptions"/> instance.
    /// Mirrors Go <c>ProcessConfigFile</c> and <c>Options.ProcessConfigFile</c>.
    /// </summary>
    public static ServerOptions ProcessConfigFile(string path)
    {
        if (!File.Exists(path))
            throw new FileNotFoundException($"Configuration file not found: {path}", path);

        var json = File.ReadAllText(path, Encoding.UTF8);
        return ProcessConfigString(json);
    }

    /// <summary>
    /// Deserialises a JSON string and returns a bound <see cref="ServerOptions"/> instance.
    /// Mirrors Go <c>Options.ProcessConfigString</c>.
    /// </summary>
    public static ServerOptions ProcessConfigString(string json)
    {
        ArgumentNullException.ThrowIfNullOrEmpty(json);
        var opts = JsonSerializer.Deserialize<ServerOptions>(json, CreateJsonOptions())
            ?? new ServerOptions();
        PostProcess(opts);
        return opts;
    }

    /// <summary>
    /// Binds a pre-built <see cref="IConfiguration"/> (e.g. from an ASP.NET Core host)
    /// to a <see cref="ServerOptions"/> instance.
    /// The configuration section should be the root or a named section such as "NatsServer".
    /// </summary>
    public static void BindConfiguration(IConfiguration config, ServerOptions target)
    {
        ArgumentNullException.ThrowIfNull(config);
        ArgumentNullException.ThrowIfNull(target);
        config.Bind(target);
        PostProcess(target);
    }

    // -------------------------------------------------------------------------
    // Post-processing
    // -------------------------------------------------------------------------

    /// <summary>
    /// Applies defaults and cross-field validation after loading.
    /// Mirrors the end of <c>Options.processConfigFile</c> and
    /// <c>configureSystemAccount</c> in server/opts.go.
    /// </summary>
    private static void PostProcess(ServerOptions opts)
    {
        // Apply default port if not set.
        if (opts.Port == 0) opts.Port = ServerConstants.DefaultPort;

        // Apply default host if not set.
        if (string.IsNullOrEmpty(opts.Host)) opts.Host = ServerConstants.DefaultHost;

        // Apply default max payload.
        if (opts.MaxPayload == 0) opts.MaxPayload = ServerConstants.MaxPayload;

        // Apply default auth timeout.
        if (opts.AuthTimeout == 0) opts.AuthTimeout = ServerConstants.DefaultAuthTimeout;

        // Apply default max control line size.
        if (opts.MaxControlLine == 0) opts.MaxControlLine = ServerConstants.MaxControlLineSize;

        // Ensure SystemAccount defaults if not set.
        ConfigureSystemAccount(opts);
    }

    /// <summary>
    /// Sets up the system account name from options.
    /// Mirrors Go <c>configureSystemAccount</c> in server/opts.go.
    /// </summary>
    private static void ConfigureSystemAccount(ServerOptions opts)
    {
        // If system account already set, nothing to do.
        if (!string.IsNullOrEmpty(opts.SystemAccount)) return;
        // Respect explicit opt-out.
        if (opts.NoSystemAccount) return;
        // Default to "$SYS" if not explicitly disabled.
        opts.SystemAccount = ServerConstants.DefaultSystemAccount;
    }
}
