using System.Text.Json;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.Core.Resilience;

/// <summary>
///     Parses the <c>DriverInstance.ResilienceConfig</c> JSON column into a
///     <see cref="DriverResilienceOptions"/> instance layered on top of the tier defaults.
///     Every key in the JSON is optional; missing keys fall back to the tier defaults from
///     <see cref="DriverResilienceOptions.GetTierDefaults(DriverTier)"/>.
/// </summary>
/// <remarks>
///     <para>Example JSON shape per Phase 6.1 Stream A.2:</para>
///     <code>
///     {
///       "bulkheadMaxConcurrent": 16,
///       "bulkheadMaxQueue": 64,
///       "capabilityPolicies": {
///         "Read":  { "timeoutSeconds": 5, "retryCount": 5, "breakerFailureThreshold": 3 },
///         "Write": { "timeoutSeconds": 5, "retryCount": 0, "breakerFailureThreshold": 5 }
///       }
///     }
///     </code>
///
///     <para>Unrecognised keys + values are ignored so future shapes land without a migration.
///     Per-capability overrides are layered on top of tier defaults — a partial policy (only
///     some of TimeoutSeconds/RetryCount/BreakerFailureThreshold) fills in the other fields
///     from the tier default for that capability.</para>
///
///     <para>Parser failures (malformed JSON, type mismatches) fall back to pure tier defaults
///     + surface through an out-parameter diagnostic. Callers may log the diagnostic but should
///     NOT fail driver startup — a misconfigured ResilienceConfig should never brick a
///     working driver.</para>
/// </remarks>
public static class DriverResilienceOptionsParser
{
    private static readonly JsonSerializerOptions JsonOpts = new()
    {
        PropertyNameCaseInsensitive = true,
        AllowTrailingCommas = true,
        ReadCommentHandling = JsonCommentHandling.Skip,
    };

    /// <summary>
    ///     Parse the JSON payload layered on <paramref name="tier"/>'s defaults. Returns the
    ///     effective options; <paramref name="parseDiagnostic"/> is null on success, or a
    ///     human-readable error message when the JSON was malformed (options still returned
    ///     = tier defaults).
    /// </summary>
    /// <param name="tier">The driver tier for default resilience options.</param>
    /// <param name="resilienceConfigJson">The optional JSON configuration string to parse.</param>
    /// <param name="parseDiagnostic">An out parameter containing diagnostic information if parsing fails.</param>
    public static DriverResilienceOptions ParseOrDefaults(
        DriverTier tier,
        string? resilienceConfigJson,
        out string? parseDiagnostic)
    {
        parseDiagnostic = null;
        var baseDefaults = DriverResilienceOptions.GetTierDefaults(tier);
        var baseOptions = new DriverResilienceOptions { Tier = tier, CapabilityPolicies = baseDefaults };

        if (string.IsNullOrWhiteSpace(resilienceConfigJson))
            return baseOptions;

        ResilienceConfigShape? shape;
        try
        {
            shape = JsonSerializer.Deserialize<ResilienceConfigShape>(resilienceConfigJson, JsonOpts);
        }
        catch (JsonException ex)
        {
            parseDiagnostic = $"ResilienceConfig JSON malformed; falling back to tier {tier} defaults. Detail: {ex.Message}";
            return baseOptions;
        }

        if (shape is null) return baseOptions;

        var merged = new Dictionary<DriverCapability, CapabilityPolicy>(baseDefaults);
        if (shape.CapabilityPolicies is not null)
        {
            foreach (var (capName, overridePolicy) in shape.CapabilityPolicies)
            {
                if (!Enum.TryParse<DriverCapability>(capName, ignoreCase: true, out var capability))
                {
                    parseDiagnostic ??= $"Unknown capability '{capName}' in ResilienceConfig; skipped.";
                    continue;
                }

                var basePolicy = merged[capability];
                merged[capability] = new CapabilityPolicy(
                    TimeoutSeconds: overridePolicy.TimeoutSeconds ?? basePolicy.TimeoutSeconds,
                    RetryCount: overridePolicy.RetryCount ?? basePolicy.RetryCount,
                    BreakerFailureThreshold: overridePolicy.BreakerFailureThreshold ?? basePolicy.BreakerFailureThreshold);
            }
        }

        // Scheduled recycle is Tier C only — reject a configured interval on Tier A/B as a
        // misconfiguration surface rather than silently honouring it (recycling an in-process
        // driver would kill every OPC UA session + every co-hosted driver, per decision #74).
        int? recycleIntervalSeconds = null;
        if (shape.RecycleIntervalSeconds is int secs)
        {
            if (secs <= 0)
                parseDiagnostic ??= $"RecycleIntervalSeconds must be positive; got {secs} — ignoring.";
            else if (tier != DriverTier.C)
                parseDiagnostic ??= $"RecycleIntervalSeconds is Tier C only; Tier {tier} driver will not scheduled-recycle.";
            else
                recycleIntervalSeconds = secs;
        }

        return new DriverResilienceOptions
        {
            Tier = tier,
            CapabilityPolicies = merged,
            BulkheadMaxConcurrent = shape.BulkheadMaxConcurrent ?? baseOptions.BulkheadMaxConcurrent,
            BulkheadMaxQueue = shape.BulkheadMaxQueue ?? baseOptions.BulkheadMaxQueue,
            RecycleIntervalSeconds = recycleIntervalSeconds,
        };
    }

    private sealed class ResilienceConfigShape
    {
        /// <summary>Gets or sets the maximum concurrent bulkhead requests.</summary>
        public int? BulkheadMaxConcurrent { get; set; }
        /// <summary>Gets or sets the maximum bulkhead queue size.</summary>
        public int? BulkheadMaxQueue { get; set; }
        /// <summary>Gets or sets the scheduled recycle interval in seconds.</summary>
        public int? RecycleIntervalSeconds { get; set; }
        /// <summary>Gets or sets the per-capability resilience policies.</summary>
        public Dictionary<string, CapabilityPolicyShape>? CapabilityPolicies { get; set; }
    }

    private sealed class CapabilityPolicyShape
    {
        /// <summary>Gets or sets the operation timeout in seconds.</summary>
        public int? TimeoutSeconds { get; set; }
        /// <summary>Gets or sets the number of retry attempts.</summary>
        public int? RetryCount { get; set; }
        /// <summary>Gets or sets the failure count threshold before the circuit breaker opens.</summary>
        public int? BreakerFailureThreshold { get; set; }
    }
}
