using System;
using System.Runtime.InteropServices;
using System.IO;
using System.Threading.Tasks;
using Serilog;
using Microsoft.Win32.SafeHandles;

namespace GeViScopeBridge.SDK
{
    /// <summary>
    /// P/Invoke wrapper for GeViAPI SetupClient functions
    /// This is what GeViSet uses to read/write configuration from/to GeViServer
    /// </summary>
    public class GeViSetupClientWrapper : IDisposable
    {
        private IntPtr _setupClientHandle = IntPtr.Zero;
        private bool _isConnected = false;
        private readonly ILogger _logger;

        // Connection parameters
        private readonly string _aliasname;
        private readonly string _address;
        private readonly string _username;
        private readonly string _password;

        #region P/Invoke Declarations

        [DllImport("GeViProcAPI.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern bool GeViAPI_SetupClient_Create(
            out IntPtr setupClient,
            [MarshalAs(UnmanagedType.LPStr)] string aliasname,
            [MarshalAs(UnmanagedType.LPStr)] string address,
            [MarshalAs(UnmanagedType.LPStr)] string username,
            [MarshalAs(UnmanagedType.LPStr)] string password,
            [MarshalAs(UnmanagedType.LPStr)] string username2,
            [MarshalAs(UnmanagedType.LPStr)] string password2
        );

        [DllImport("GeViProcAPI.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern bool GeViAPI_SetupClient_Connect(
            IntPtr setupClient,
            out int connectResult,
            IntPtr callback,  // TGeViConnectProgress callback (can be IntPtr.Zero)
            IntPtr instance   // void* instance (can be IntPtr.Zero)
        );

        [DllImport("GeViProcAPI.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern bool GeViAPI_SetupClient_Disconnect(IntPtr setupClient);

        [DllImport("GeViProcAPI.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern bool GeViAPI_SetupClient_Destroy(IntPtr setupClient);

        [DllImport("GeViProcAPI.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern bool GeViAPI_SetupClient_ReadSetup(
            IntPtr setupClient,
            IntPtr hFile  // File handle from CreateFile
        );

        [DllImport("GeViProcAPI.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern bool GeViAPI_SetupClient_WriteSetup(
            IntPtr setupClient,
            IntPtr hFile  // File handle from CreateFile
        );

        [DllImport("GeViProcAPI.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern bool GeViAPI_SetupClient_SendPing(IntPtr setupClient);

        // Windows API for file operations
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CreateFile(
            string lpFileName,
            uint dwDesiredAccess,
            uint dwShareMode,
            IntPtr lpSecurityAttributes,
            uint dwCreationDisposition,
            uint dwFlagsAndAttributes,
            IntPtr hTemplateFile
        );

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool CloseHandle(IntPtr hObject);

        // Password encoding function
        [DllImport("GeViProcAPI.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private static extern void GeViAPI_EncodeString(
            [MarshalAs(UnmanagedType.LPStr)] System.Text.StringBuilder output,
            [MarshalAs(UnmanagedType.LPStr)] string input,
            int size
        );

        // File access constants
        private const uint GENERIC_READ = 0x80000000;
        private const uint GENERIC_WRITE = 0x40000000;
        private const uint CREATE_ALWAYS = 2;
        private const uint OPEN_EXISTING = 3;
        private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);

        #endregion

        public GeViSetupClientWrapper(string address, string username, string password, string aliasname = "")
        {
            _address = address ?? throw new ArgumentNullException(nameof(address));
            _username = username ?? throw new ArgumentNullException(nameof(username));
            _password = password ?? throw new ArgumentNullException(nameof(password));
            _aliasname = aliasname ?? "";
            _logger = Log.ForContext<GeViSetupClientWrapper>();
        }

        /// <summary>
        /// Connect to GeViServer
        /// </summary>
        public async Task<bool> ConnectAsync()
        {
            return await Task.Run(() => Connect());
        }

        private bool Connect()
        {
            try
            {
                _logger.Information("Creating SetupClient for {Address}", _address);

                // Encrypt password using GeViAPI_EncodeString
                // Password buffer should be at least 256 bytes according to typical SDK usage
                var encodedPassword = new System.Text.StringBuilder(256);
                GeViAPI_EncodeString(encodedPassword, _password, encodedPassword.Capacity);

                _logger.Debug("Password encrypted for SetupClient connection");

                // Create SetupClient with encrypted password
                bool created = GeViAPI_SetupClient_Create(
                    out _setupClientHandle,
                    _aliasname,
                    _address,
                    _username,
                    encodedPassword.ToString(),  // Use encrypted password
                    "",  // username2 (optional, for dual control)
                    ""   // password2 (optional)
                );

                if (!created || _setupClientHandle == IntPtr.Zero)
                {
                    _logger.Error("Failed to create SetupClient");
                    return false;
                }

                _logger.Information("SetupClient created, connecting to {Address}", _address);

                // Connect to server
                bool connected = GeViAPI_SetupClient_Connect(
                    _setupClientHandle,
                    out int connectResult,
                    IntPtr.Zero,  // No progress callback
                    IntPtr.Zero   // No instance
                );

                if (!connected || connectResult != 0)
                {
                    string errorName = GetConnectResultName(connectResult);
                    _logger.Error("Failed to connect SetupClient. Result: {Result} ({ErrorName})", connectResult, errorName);
                    return false;
                }

                _isConnected = true;
                _logger.Information("SetupClient connected successfully to {Address}", _address);
                return true;
            }
            catch (Exception ex)
            {
                _logger.Error(ex, "Exception during SetupClient connection");
                return false;
            }
        }

        /// <summary>
        /// Read complete setup configuration from GeViServer to a file
        /// </summary>
        public async Task<byte[]> ReadSetupAsync()
        {
            return await Task.Run(() => ReadSetup());
        }

        private byte[] ReadSetup()
        {
            if (!_isConnected || _setupClientHandle == IntPtr.Zero)
            {
                throw new InvalidOperationException("SetupClient is not connected");
            }

            string tempFile = Path.GetTempFileName();

            try
            {
                _logger.Information("Reading setup configuration from GeViServer to {TempFile}", tempFile);

                // Create file handle for writing
                IntPtr hFile = CreateFile(
                    tempFile,
                    GENERIC_WRITE,
                    0,  // No sharing
                    IntPtr.Zero,
                    CREATE_ALWAYS,
                    0,
                    IntPtr.Zero
                );

                if (hFile == INVALID_HANDLE_VALUE)
                {
                    int error = Marshal.GetLastWin32Error();
                    throw new IOException($"Failed to create temp file. Error: {error}");
                }

                try
                {
                    // Read setup from server
                    bool success = GeViAPI_SetupClient_ReadSetup(_setupClientHandle, hFile);

                    if (!success)
                    {
                        throw new InvalidOperationException("Failed to read setup from GeViServer");
                    }

                    _logger.Information("Setup configuration read successfully");
                }
                finally
                {
                    CloseHandle(hFile);
                }

                // Read file contents
                byte[] data = File.ReadAllBytes(tempFile);
                _logger.Information("Read {Size} bytes of setup configuration", data.Length);
                return data;
            }
            finally
            {
                // Clean up temp file
                if (File.Exists(tempFile))
                {
                    try
                    {
                        File.Delete(tempFile);
                    }
                    catch (Exception ex)
                    {
                        _logger.Warning(ex, "Failed to delete temp file {TempFile}", tempFile);
                    }
                }
            }
        }

        /// <summary>
        /// Write setup configuration back to GeViServer from a byte array
        /// </summary>
        public async Task<bool> WriteSetupAsync(byte[] setupData)
        {
            return await Task.Run(() => WriteSetup(setupData));
        }

        private bool WriteSetup(byte[] setupData)
        {
            if (!_isConnected || _setupClientHandle == IntPtr.Zero)
            {
                throw new InvalidOperationException("SetupClient is not connected");
            }

            if (setupData == null || setupData.Length == 0)
            {
                throw new ArgumentException("Setup data cannot be null or empty", nameof(setupData));
            }

            string tempFile = Path.GetTempFileName();

            try
            {
                _logger.Information("Writing {Size} bytes of setup configuration to GeViServer", setupData.Length);

                // Write data to temp file
                File.WriteAllBytes(tempFile, setupData);

                // Open file handle for reading
                IntPtr hFile = CreateFile(
                    tempFile,
                    GENERIC_READ,
                    0,  // No sharing
                    IntPtr.Zero,
                    OPEN_EXISTING,
                    0,
                    IntPtr.Zero
                );

                if (hFile == INVALID_HANDLE_VALUE)
                {
                    int error = Marshal.GetLastWin32Error();
                    throw new IOException($"Failed to open temp file. Error: {error}");
                }

                try
                {
                    // Write setup to server
                    bool success = GeViAPI_SetupClient_WriteSetup(_setupClientHandle, hFile);

                    if (!success)
                    {
                        _logger.Error("Failed to write setup to GeViServer");
                        return false;
                    }

                    _logger.Information("Setup configuration written successfully to GeViServer");
                    return true;
                }
                finally
                {
                    CloseHandle(hFile);
                }
            }
            finally
            {
                // Clean up temp file
                if (File.Exists(tempFile))
                {
                    try
                    {
                        File.Delete(tempFile);
                    }
                    catch (Exception ex)
                    {
                        _logger.Warning(ex, "Failed to delete temp file {TempFile}", tempFile);
                    }
                }
            }
        }

        /// <summary>
        /// Send ping to keep connection alive
        /// </summary>
        public bool SendPing()
        {
            if (!_isConnected || _setupClientHandle == IntPtr.Zero)
            {
                return false;
            }

            return GeViAPI_SetupClient_SendPing(_setupClientHandle);
        }

        /// <summary>
        /// Disconnect from GeViServer
        /// </summary>
        public void Disconnect()
        {
            if (_isConnected && _setupClientHandle != IntPtr.Zero)
            {
                _logger.Information("Disconnecting SetupClient");
                GeViAPI_SetupClient_Disconnect(_setupClientHandle);
                _isConnected = false;
            }
        }

        public void Dispose()
        {
            Disconnect();

            if (_setupClientHandle != IntPtr.Zero)
            {
                _logger.Information("Destroying SetupClient");
                GeViAPI_SetupClient_Destroy(_setupClientHandle);
                _setupClientHandle = IntPtr.Zero;
            }
        }

        private static string GetConnectResultName(int result)
        {
            return result switch
            {
                0 => "connectOk",
                100 => "connectAborted",
                101 => "connectGenericError",
                300 => "connectRemoteUnknownError",
                301 => "connectRemoteTcpError",
                302 => "connectRemoteUnknownUser",
                303 => "connectRemoteConnectionLimitExceeded",
                304 => "connectRemoteClientInterfaceTooOld",
                305 => "connectRemoteServerInterfaceTooOld",
                306 => "connectRemoteSecondUserRequired",
                307 => "connectRemotePortDisabled",
                _ => $"Unknown({result})"
            };
        }
    }
}
