using System.Text;
using NATS.Server.Configuration;
using NATS.Server.JetStream.Api;
using NATS.Server.JetStream.Models;

namespace NATS.Server.JetStream.Validation;

public static class JetStreamConfigValidator
{
    public static bool IsValidName(string? name)
    {
        if (string.IsNullOrWhiteSpace(name))
            return false;

        // Go len(name) checks byte length, not character length.
        if (Encoding.UTF8.GetByteCount(name) > JetStreamApiLimits.JSMaxNameLen)
            return false;

        foreach (var ch in name)
        {
            if (char.IsWhiteSpace(ch) || ch is '*' or '>')
                return false;
        }

        return true;
    }

    public static bool IsMetadataWithinLimit(Dictionary<string, string>? metadata)
        => MetadataByteSize(metadata) <= JetStreamApiLimits.JSMaxMetadataLen;

    public static int MetadataByteSize(Dictionary<string, string>? metadata)
    {
        if (metadata is null || metadata.Count == 0)
            return 0;

        var size = 0;
        foreach (var (key, value) in metadata)
        {
            size += Encoding.UTF8.GetByteCount(key);
            size += Encoding.UTF8.GetByteCount(value);
        }

        return size;
    }

    public static ValidationResult Validate(StreamConfig config)
    {
        if (string.IsNullOrWhiteSpace(config.Name) || config.Subjects.Count == 0)
            return ValidationResult.Invalid("name/subjects required");

        if (config.Retention == RetentionPolicy.WorkQueue && config.MaxConsumers == 0)
            return ValidationResult.Invalid("workqueue retention requires max consumers > 0");
        if (config.MaxMsgSize < 0)
            return ValidationResult.Invalid("max_msg_size must be >= 0");
        if (config.MaxMsgsPer < 0)
            return ValidationResult.Invalid("max_msgs_per must be >= 0");
        if (config.MaxAgeMs < 0)
            return ValidationResult.Invalid("max_age_ms must be >= 0");

        return ValidationResult.Valid();
    }

    /// <summary>
    /// Validates JetStream cluster configuration requirements.
    /// When JetStream is enabled and clustering is configured (Cluster.Port > 0),
    /// both server_name and cluster.name must be set.
    /// Reference: Go server/jetstream.go validateOptions (line ~2822-2831).
    /// </summary>
    public static ValidationResult ValidateClusterConfig(NatsOptions options)
    {
        // If JetStream is not enabled or not clustered, no cluster-specific checks needed.
        if (options.JetStream == null || options.Cluster == null || options.Cluster.Port == 0)
            return ValidationResult.Valid();

        if (string.IsNullOrEmpty(options.ServerName))
            return ValidationResult.Invalid("jetstream cluster requires `server_name` to be set");

        if (string.IsNullOrEmpty(options.Cluster.Name))
            return ValidationResult.Invalid("jetstream cluster requires `cluster.name` to be set");

        return ValidationResult.Valid();
    }
}

public sealed class ValidationResult
{
    public bool IsValid { get; }
    public string Message { get; }

    private ValidationResult(bool isValid, string message)
    {
        IsValid = isValid;
        Message = message;
    }

    public static ValidationResult Valid() => new(true, string.Empty);
    public static ValidationResult Invalid(string message) => new(false, message);
}
