using System;
using System.Collections.Generic;
using System.Linq;

namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Backend
{
    /// <summary>
    ///     Thread-safe, pure-logic endpoint picker for the Wonderware Historian cluster. Tracks which
    ///     configured nodes are healthy, places failed nodes in a time-bounded cooldown, and hands
    ///     out an ordered list of eligible candidates for the data source to try in sequence.
    /// </summary>
    internal sealed class HistorianClusterEndpointPicker
    {
        private readonly Func<DateTime> _clock;
        private readonly TimeSpan _cooldown;
        private readonly object _lock = new object();
        private readonly List<NodeEntry> _nodes;

        /// <summary>Initializes the picker with default system clock.</summary>
        /// <param name="config">Historian configuration.</param>
        public HistorianClusterEndpointPicker(HistorianConfiguration config)
            : this(config, () => DateTime.UtcNow) { }

        /// <summary>Initializes the picker with custom clock function.</summary>
        /// <param name="config">Historian configuration.</param>
        /// <param name="clock">Clock function for testing.</param>
        internal HistorianClusterEndpointPicker(HistorianConfiguration config, Func<DateTime> clock)
        {
            _clock = clock ?? throw new ArgumentNullException(nameof(clock));
            _cooldown = TimeSpan.FromSeconds(Math.Max(0, config.FailureCooldownSeconds));

            var names = (config.ServerNames != null && config.ServerNames.Count > 0)
                ? config.ServerNames
                : new List<string> { config.ServerName };

            _nodes = names
                .Where(n => !string.IsNullOrWhiteSpace(n))
                .Select(n => n.Trim())
                .Distinct(StringComparer.OrdinalIgnoreCase)
                .Select(n => new NodeEntry { Name = n })
                .ToList();
        }

        /// <summary>Gets the total count of configured nodes.</summary>
        public int NodeCount
        {
            get { lock (_lock) return _nodes.Count; }
        }

        /// <summary>Gets the list of currently healthy nodes.</summary>
        public IReadOnlyList<string> GetHealthyNodes()
        {
            lock (_lock)
            {
                var now = _clock();
                return _nodes.Where(n => IsHealthyAt(n, now)).Select(n => n.Name).ToList();
            }
        }

        /// <summary>Gets the count of currently healthy nodes.</summary>
        public int HealthyNodeCount
        {
            get
            {
                lock (_lock)
                {
                    var now = _clock();
                    return _nodes.Count(n => IsHealthyAt(n, now));
                }
            }
        }

        /// <summary>Marks a node as failed and starts its cooldown.</summary>
        /// <param name="node">Node name.</param>
        /// <param name="error">Optional error message.</param>
        public void MarkFailed(string node, string? error)
        {
            lock (_lock)
            {
                var entry = FindEntry(node);
                if (entry == null) return;

                var now = _clock();
                entry.FailureCount++;
                entry.LastError = error;
                entry.LastFailureTime = now;
                entry.CooldownUntil = _cooldown.TotalMilliseconds > 0 ? now + _cooldown : (DateTime?)null;
            }
        }

        /// <summary>Marks a node as healthy and clears its cooldown.</summary>
        /// <param name="node">Node name.</param>
        public void MarkHealthy(string node)
        {
            lock (_lock)
            {
                var entry = FindEntry(node);
                if (entry == null) return;
                entry.CooldownUntil = null;
            }
        }

        /// <summary>Returns a snapshot of all node states.</summary>
        public List<HistorianClusterNodeState> SnapshotNodeStates()
        {
            lock (_lock)
            {
                var now = _clock();
                return _nodes.Select(n => new HistorianClusterNodeState
                {
                    Name = n.Name,
                    IsHealthy = IsHealthyAt(n, now),
                    CooldownUntil = IsHealthyAt(n, now) ? null : n.CooldownUntil,
                    FailureCount = n.FailureCount,
                    LastError = n.LastError,
                    LastFailureTime = n.LastFailureTime
                }).ToList();
            }
        }

        private static bool IsHealthyAt(NodeEntry entry, DateTime now)
        {
            return entry.CooldownUntil == null || entry.CooldownUntil <= now;
        }

        private NodeEntry? FindEntry(string node)
        {
            for (var i = 0; i < _nodes.Count; i++)
                if (string.Equals(_nodes[i].Name, node, StringComparison.OrdinalIgnoreCase))
                    return _nodes[i];
            return null;
        }

        private sealed class NodeEntry
        {
            /// <summary>Gets or sets the node name.</summary>
            public string Name { get; set; } = "";
            /// <summary>Gets or sets when cooldown expires.</summary>
            public DateTime? CooldownUntil { get; set; }
            /// <summary>Gets or sets the failure count.</summary>
            public int FailureCount { get; set; }
            /// <summary>Gets or sets the last error message.</summary>
            public string? LastError { get; set; }
            /// <summary>Gets or sets the last failure time.</summary>
            public DateTime? LastFailureTime { get; set; }
        }
    }
}
