using System;
using System.Threading;
using ArchestrA;

namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Backend
{
    /// <summary>
    ///     Creates and opens Historian SDK connections. Extracted so tests can inject fakes that
    ///     control connection success, failure, and timeout behavior.
    /// </summary>
    internal interface IHistorianConnectionFactory
    {
        /// <summary>
        ///     Opens a Historian SDK connection. <paramref name="readOnly"/> defaults to
        ///     <c>true</c> for the query path; the alarm-event write backend passes
        ///     <c>false</c> because <c>HistorianAccess.AddStreamedValue</c> fails with
        ///     <c>WriteToReadOnlyFile</c> on a read-only session.
        /// </summary>
        /// <param name="config">The historian configuration.</param>
        /// <param name="type">The type of connection to create.</param>
        /// <param name="readOnly">Whether the connection should be read-only.</param>
        /// <returns>An open HistorianAccess connection.</returns>
        HistorianAccess CreateAndConnect(
            HistorianConfiguration config, HistorianConnectionType type, bool readOnly = true);
    }

    /// <summary>Production implementation — opens real Historian SDK connections.</summary>
    internal sealed class SdkHistorianConnectionFactory : IHistorianConnectionFactory
    {
        /// <summary>Creates and connects a Historian SDK connection.</summary>
        /// <param name="config">The historian configuration.</param>
        /// <param name="type">The type of connection to create.</param>
        /// <param name="readOnly">Whether the connection should be read-only.</param>
        /// <returns>An open HistorianAccess connection.</returns>
        public HistorianAccess CreateAndConnect(
            HistorianConfiguration config, HistorianConnectionType type, bool readOnly = true)
        {
            var conn = new HistorianAccess();
            var args = BuildConnectionArgs(config, type, readOnly);

            if (!conn.OpenConnection(args, out var error))
            {
                conn.Dispose();
                throw new InvalidOperationException(
                    $"Failed to open Historian SDK connection to {config.ServerName}:{config.Port}: {error.ErrorCode}");
            }

            var timeoutMs = config.CommandTimeoutSeconds * 1000;
            var elapsed = 0;
            while (elapsed < timeoutMs)
            {
                var status = new HistorianConnectionStatus();
                conn.GetConnectionStatus(ref status);

                if (status.ConnectedToServer)
                    return conn;

                if (status.ErrorOccurred)
                {
                    conn.Dispose();
                    throw new InvalidOperationException(
                        $"Historian SDK connection failed: {status.Error}");
                }

                Thread.Sleep(250);
                elapsed += 250;
            }

            conn.Dispose();
            throw new TimeoutException(
                $"Historian SDK connection to {config.ServerName}:{config.Port} timed out after {config.CommandTimeoutSeconds}s");
        }

        /// <summary>
        ///     Builds the <see cref="HistorianConnectionArgs"/> for a connection. Pure (no SDK
        ///     side effects) so the read-only-vs-write argument shaping is unit-testable.
        /// </summary>
        /// <param name="config">The historian configuration.</param>
        /// <param name="type">The type of connection to create.</param>
        /// <param name="readOnly">Whether the connection should be read-only.</param>
        /// <returns>The configured connection arguments.</returns>
        internal static HistorianConnectionArgs BuildConnectionArgs(
            HistorianConfiguration config, HistorianConnectionType type, bool readOnly)
        {
            var args = new HistorianConnectionArgs
            {
                ServerName = config.ServerName,
                TcpPort = (ushort)config.Port,
                IntegratedSecurity = config.IntegratedSecurity,
                UseArchestrAUser = config.IntegratedSecurity,
                ConnectionType = type,
                ReadOnly = readOnly,
                PacketTimeout = (uint)(config.CommandTimeoutSeconds * 1000)
            };

            if (!config.IntegratedSecurity)
            {
                args.UserName = config.UserName ?? string.Empty;
                args.Password = config.Password ?? string.Empty;
            }

            return args;
        }
    }
}
