using System;
using System.Runtime.InteropServices;
using ArchestrA.GRAccess;
using Serilog;

namespace ZB.MOM.WW.GRAccess.Cli.GRAccess
{
    public sealed class GRAccessConnection : IDisposable
    {
        private static readonly ILogger Log = Serilog.Log.ForContext<GRAccessConnection>();

        private GRAccessApp _grAccessApp;

        public string GalaxyName { get; }
        public string NodeName { get; }
        public IGalaxy Galaxy { get; private set; }
        public bool IsConnected { get; private set; }

        public GRAccessConnection(string galaxyName, string nodeName)
        {
            GalaxyName = galaxyName ?? throw new ArgumentNullException(nameof(galaxyName));
            NodeName = GRAccessDiagnostics.NormalizeNodeName(
                nodeName ?? throw new ArgumentNullException(nameof(nodeName)));
        }

        /// <summary>
        /// Must be called on an STA thread.
        /// </summary>
        public void Connect()
        {
            Log.Information("Connecting to galaxy {Galaxy} on {Node}", GalaxyName, NodeName);

            _grAccessApp = new GRAccessAppClass();

            var galaxies = _grAccessApp.QueryGalaxies(NodeName);
            GRAccessDiagnostics.ThrowIfFailed(_grAccessApp.CommandResult, "QueryGalaxies");

            if (galaxies == null || galaxies.count == 0)
                throw new InvalidOperationException($"No galaxies found on node '{NodeName}'.");

            Galaxy = galaxies[GalaxyName];
            if (Galaxy == null)
                throw new InvalidOperationException($"Galaxy '{GalaxyName}' not found on node '{NodeName}'.");

            Galaxy.Login("", "");
            GRAccessDiagnostics.ThrowIfFailed(Galaxy.CommandResult, "Login");

            IsConnected = true;
            Log.Information("Connected to galaxy {Galaxy}", GalaxyName);
        }

        /// <summary>
        /// Must be called on an STA thread.
        /// </summary>
        public void Disconnect()
        {
            if (!IsConnected && Galaxy == null && _grAccessApp == null)
                return;

            try
            {
                if (IsConnected)
                {
                    Galaxy?.Logout();
                    Log.Information("Disconnected from galaxy {Galaxy}", GalaxyName);
                }
            }
            catch (Exception ex)
            {
                Log.Warning(ex, "Error during galaxy logout");
            }
            finally
            {
                ReleaseComObject(Galaxy);
                ReleaseComObject(_grAccessApp);
                Galaxy = null;
                _grAccessApp = null;
                IsConnected = false;
            }
        }

        public void Dispose()
        {
            Disconnect();
        }

        private static void ReleaseComObject(object value)
        {
            if (value == null || !Marshal.IsComObject(value))
                return;

            try
            {
                Marshal.FinalReleaseComObject(value);
            }
            catch (Exception ex)
            {
                Log.Warning(ex, "Error releasing GRAccess COM object");
            }
        }
    }
}
