#include "../std_include.hpp"
#include "../emulator_utils.hpp"
#include "../syscall_utils.hpp"

#include <utils/finally.hpp>

namespace syscalls
{
    NTSTATUS handle_NtQueryInformationProcess(const syscall_context& c, const handle process_handle, const uint32_t info_class,
                                              const uint64_t process_information, const uint32_t process_information_length,
                                              const emulator_object<uint32_t> return_length)
    {
        if (process_handle != CURRENT_PROCESS)
        {
            return STATUS_NOT_SUPPORTED;
        }

        const auto return_length_info = c.win_emu.memory.get_region_info(return_length.value());

        switch (info_class)
        {
        case ProcessExecuteFlags:
            return STATUS_NOT_SUPPORTED;
        case ProcessGroupInformation:
        case ProcessMitigationPolicy: {
            // ProcessMitigationPolicy requires special handling because the caller
            // specifies which policy to query via the Policy field in the input buffer.
            // We need to read this field first to determine what's being queried.

            // Ensure we have at least enough space to read the Policy field
            if (process_information_length < sizeof(PROCESS_MITIGATION_POLICY))
            {
                return STATUS_BUFFER_TOO_SMALL;
            }

            // Read the policy type from the input buffer using safe emulator memory access
            const emulator_object<PROCESS_MITIGATION_POLICY> policy_obj{c.emu, process_information};
            const auto policy = policy_obj.read();

            // We only support querying ProcessDynamicCodePolicy
            if (policy != ProcessDynamicCodePolicy)
            {
                return STATUS_NOT_SUPPORTED;
            }

            return handle_query<PROCESS_MITIGATION_POLICY_RAW_DATA>(c.emu, process_information, process_information_length, return_length,
                                                                    [policy](PROCESS_MITIGATION_POLICY_RAW_DATA& policy_data) {
                                                                        policy_data.Policy = policy;
                                                                        policy_data.Value = 0;
                                                                    });
        }
        case ProcessEnclaveInformation:
            return STATUS_NOT_SUPPORTED;

        case ProcessTimes:
            return handle_query<KERNEL_USER_TIMES>(c.emu, process_information, process_information_length, return_length,
                                                   [](KERNEL_USER_TIMES& t) {
                                                       t = {}; //
                                                   });

        case ProcessCookie:
            return handle_query<uint32_t>(c.emu, process_information, process_information_length, return_length, [](uint32_t& cookie) {
                cookie = 0x01234567; //
            });

        case ProcessDebugObjectHandle:

            c.win_emu.callbacks.on_suspicious_activity("Anti-debug check with ProcessDebugObjectHandle");

            if ((process_information & 3) != 0)
            {
                return STATUS_DATATYPE_MISALIGNMENT;
            }

            if (return_length.value() == 0)
            {
                return STATUS_PORT_NOT_SET;
            }

            if (!return_length_info.is_reserved)
            {
                return STATUS_ACCESS_VIOLATION;
            }

            return handle_query<handle>(c.emu, process_information, process_information_length, return_length, [](handle& h) {
                h = NULL_HANDLE;
                return STATUS_PORT_NOT_SET;
            });

        case ProcessDebugFlags:
        case ProcessWx86Information:
        case ProcessDefaultHardErrorMode:
            return handle_query<ULONG>(c.emu, process_information, process_information_length, return_length, [&](ULONG& res) {
                res = (info_class == ProcessDebugFlags ? 1 : 0); //
            });

        case ProcessDebugPort:
            c.win_emu.callbacks.on_suspicious_activity("Anti-debug check with ProcessDebugPort");

            return handle_query<EmulatorTraits<Emu64>::PVOID>(c.emu, process_information, process_information_length, return_length,
                                                              [](EmulatorTraits<Emu64>::PVOID& ptr) {
                                                                  ptr = 0; //
                                                              });

        case ProcessDeviceMap:
            return handle_query<EmulatorTraits<Emu64>::PVOID>(c.emu, process_information, process_information_length, return_length,
                                                              [](EmulatorTraits<Emu64>::PVOID& ptr) {
                                                                  ptr = 0; //
                                                              });

        case ProcessEnableAlignmentFaultFixup:
            return handle_query<BOOLEAN>(c.emu, process_information, process_information_length, return_length, [](BOOLEAN& b) {
                b = FALSE; //
            });

        case ProcessPriorityClass:
            return handle_query<PROCESS_PRIORITY_CLASS>(c.emu, process_information, process_information_length, return_length,
                                                        [](PROCESS_PRIORITY_CLASS& c) {
                                                            c.Foreground = 1;
                                                            c.PriorityClass = 32; // Normal
                                                        });

        case ProcessBasicInformation: {
            const auto init_basic_info = [&](PROCESS_BASIC_INFORMATION64& basic_info) {
                basic_info.PebBaseAddress = c.proc.peb64.value();
                basic_info.UniqueProcessId = 1;
            };

            switch (process_information_length)
            {
            case sizeof(PROCESS_BASIC_INFORMATION64):
                return handle_query<PROCESS_BASIC_INFORMATION64>(c.emu, process_information, process_information_length, return_length,
                                                                 init_basic_info);
            case sizeof(PROCESS_EXTENDED_BASIC_INFORMATION):
                return handle_query<PROCESS_EXTENDED_BASIC_INFORMATION>(
                    c.emu, process_information, process_information_length, return_length,
                    [&](PROCESS_EXTENDED_BASIC_INFORMATION& ext_basic_info) {
                        ext_basic_info.Size = sizeof(PROCESS_EXTENDED_BASIC_INFORMATION);
                        init_basic_info(ext_basic_info.BasicInfo);
                    });
            default:
                return STATUS_INFO_LENGTH_MISMATCH;
            }
        }

        case ProcessImageInformation:
            return handle_query<SECTION_IMAGE_INFORMATION<EmulatorTraits<Emu64>>>(
                c.emu, process_information, process_information_length, return_length,
                [&](SECTION_IMAGE_INFORMATION<EmulatorTraits<Emu64>>& i) {
                    const auto& mod = *c.win_emu.mod_manager.executable;

                    const emulator_object<PEDosHeader_t> dos_header_obj{c.emu, mod.image_base};
                    const auto dos_header = dos_header_obj.read();

                    const emulator_object<PENTHeaders_t<uint64_t>> nt_headers_obj{c.emu, mod.image_base + dos_header.e_lfanew};
                    const auto nt_headers = nt_headers_obj.read();

                    const auto& file_header = nt_headers.FileHeader;
                    const auto& optional_header = nt_headers.OptionalHeader;

                    i.TransferAddress = 0;
                    i.MaximumStackSize = optional_header.SizeOfStackReserve;
                    i.CommittedStackSize = optional_header.SizeOfStackCommit;
                    i.SubSystemType = optional_header.Subsystem;
                    i.SubSystemMajorVersion = optional_header.MajorSubsystemVersion;
                    i.SubSystemMinorVersion = optional_header.MinorSubsystemVersion;
                    i.MajorOperatingSystemVersion = optional_header.MajorOperatingSystemVersion;
                    i.MinorOperatingSystemVersion = optional_header.MinorOperatingSystemVersion;
                    i.ImageCharacteristics = file_header.Characteristics;
                    i.DllCharacteristics = optional_header.DllCharacteristics;
                    i.Machine = file_header.Machine;
                    i.ImageContainsCode = TRUE;
                    i.ImageFlags = 0; // TODO
                    i.ImageFileSize = optional_header.SizeOfImage;
                    i.LoaderFlags = optional_header.LoaderFlags;
                    i.CheckSum = optional_header.CheckSum;
                });

        case ProcessImageFileNameWin32: {
            const auto peb = c.proc.peb64.read();
            emulator_object<RTL_USER_PROCESS_PARAMETERS64> proc_params{c.emu, peb.ProcessParameters};
            const auto params = proc_params.read();
            const auto length = params.ImagePathName.Length + sizeof(UNICODE_STRING<EmulatorTraits<Emu64>>) + 2;

            if (return_length)
            {
                return_length.write(static_cast<uint32_t>(length));
            }

            if (process_information_length < length)
            {
                return STATUS_BUFFER_OVERFLOW;
            }

            const emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> info{c.emu, process_information};
            info.access([&](UNICODE_STRING<EmulatorTraits<Emu64>>& str) {
                const auto buffer_start = static_cast<uint64_t>(process_information) + sizeof(UNICODE_STRING<EmulatorTraits<Emu64>>);
                const auto string = read_unicode_string(c.emu, params.ImagePathName);
                c.emu.write_memory(buffer_start, string.c_str(), (string.size() + 1) * 2);
                str.Length = params.ImagePathName.Length;
                str.MaximumLength = str.Length;
                str.Buffer = buffer_start;
            });

            return STATUS_SUCCESS;
        }

        default:
            c.win_emu.log.error("Unsupported process info class: %X\n", info_class);
            c.emu.stop();

            return STATUS_NOT_SUPPORTED;
        }
    }

    NTSTATUS handle_NtSetInformationProcess(const syscall_context& c, const handle process_handle, const uint32_t info_class,
                                            const uint64_t process_information, const uint32_t process_information_length)
    {
        if (process_handle != CURRENT_PROCESS)
        {
            return STATUS_NOT_SUPPORTED;
        }

        if (info_class == ProcessSchedulerSharedData                     //
            || info_class == ProcessConsoleHostProcess                   //
            || info_class == ProcessFaultInformation                     //
            || info_class == ProcessDefaultHardErrorMode                 //
            || info_class == ProcessRaiseUMExceptionOnInvalidHandleClose //
            || info_class == ProcessDynamicFunctionTableInformation      //
            || info_class == ProcessPriorityBoost                        //
            || info_class == ProcessPriorityClassEx                      //
            || info_class == ProcessPriorityClass || info_class == ProcessAffinityMask)
        {
            return STATUS_SUCCESS;
        }

        if (info_class == ProcessExecuteFlags)
        {
            return STATUS_NOT_SUPPORTED;
        }

        if (info_class == ProcessTlsInformation)
        {
            if (process_information_length < sizeof(PROCESS_TLS_INFORMATION) ||
                (process_information_length - (sizeof(PROCESS_TLS_INFORMATION) - sizeof(THREAD_TLS_INFORMATION))) %
                    sizeof(THREAD_TLS_INFORMATION))
            {
                return STATUS_INFO_LENGTH_MISMATCH;
            }

            constexpr auto thread_data_offset = offsetof(PROCESS_TLS_INFORMATION, ThreadData);
            const emulator_object<THREAD_TLS_INFORMATION> data{c.emu, process_information + thread_data_offset};

            PROCESS_TLS_INFORMATION tls_info{};
            c.emu.read_memory(process_information, &tls_info, thread_data_offset);

            for (uint32_t i = 0; i < tls_info.ThreadDataCount; ++i)
            {
                auto entry = data.read(i);

                const auto _ = utils::finally([&] { data.write(entry, i); });

                if (i >= c.proc.threads.size())
                {
                    entry.Flags = 0;
                    continue;
                }

                auto thread_iterator = c.proc.threads.begin();
                std::advance(thread_iterator, i);

                entry.Flags = 2;

                const auto is_wow64 = c.win_emu.process.is_wow64_process;
                const auto& thread = thread_iterator->second;

                thread.teb64->access([&](TEB64& teb) {
                    entry.ThreadId = teb.ClientId.UniqueThread;

                    uint64_t tls_vector = teb.ThreadLocalStoragePointer;
                    const auto ptr_size = is_wow64 ? sizeof(EmulatorTraits<Emu32>::PVOID) : sizeof(EmulatorTraits<Emu64>::PVOID);

                    if (is_wow64)
                    {
                        if (!thread.teb32.has_value())
                        {
                            return;
                        }

                        thread.teb32->access([&tls_vector](const TEB32& teb32) { tls_vector = teb32.ThreadLocalStoragePointer; });
                    }

                    if (!tls_vector)
                    {
                        return;
                    }

                    if (tls_info.OperationType == ProcessTlsReplaceIndex)
                    {
                        const auto tls_entry_ptr = tls_vector + (tls_info.TlsIndex * ptr_size);
                        uint64_t old_entry{};

                        if (is_wow64)
                        {
                            old_entry = c.emu.read_memory<EmulatorTraits<Emu32>::PVOID>(tls_entry_ptr);
                            c.emu.write_memory<EmulatorTraits<Emu32>::PVOID>(tls_entry_ptr, static_cast<uint32_t>(entry.NewTlsData));
                        }
                        else
                        {
                            old_entry = c.emu.read_memory<EmulatorTraits<Emu64>::PVOID>(tls_entry_ptr);
                            c.emu.write_memory<EmulatorTraits<Emu64>::PVOID>(tls_entry_ptr, entry.NewTlsData);
                        }

                        entry.OldTlsData = old_entry;
                    }
                    else if (tls_info.OperationType == ProcessTlsReplaceVector)
                    {
                        const auto new_tls_vector = entry.NewTlsData;

                        for (uint32_t index = 0; index < tls_info.PreviousCount; ++index)
                        {
                            if (is_wow64)
                            {
                                const auto old_entry = c.emu.read_memory<uint32_t>(tls_vector + (index * ptr_size));
                                c.emu.write_memory(new_tls_vector + (index * ptr_size), old_entry);
                            }
                            else
                            {
                                const auto old_entry = c.emu.read_memory<uint64_t>(tls_vector + (index * ptr_size));
                                c.emu.write_memory(new_tls_vector + (index * ptr_size), old_entry);
                            }
                        }

                        if (is_wow64)
                        {
                            thread.teb32->access([&new_tls_vector](TEB32& teb32) {
                                teb32.ThreadLocalStoragePointer = static_cast<uint32_t>(new_tls_vector);
                            });
                        }
                        else
                        {
                            teb.ThreadLocalStoragePointer = new_tls_vector;
                        }

                        entry.OldTlsData = tls_vector;
                    }
                });
            }

            return STATUS_SUCCESS;
        }

        if (info_class == ProcessInstrumentationCallback)
        {
            if (process_information_length != sizeof(PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION))
            {
                return STATUS_BUFFER_OVERFLOW;
            }

            PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION info;

            c.emu.read_memory(process_information, &info, sizeof(PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION));
            c.win_emu.callbacks.on_suspicious_activity("Setting ProcessInstrumentationCallback");

            c.proc.instrumentation_callback = info.Callback;

            return STATUS_SUCCESS;
        }

        c.win_emu.log.error("Unsupported info process class: %X\n", info_class);
        c.emu.stop();

        return STATUS_NOT_SUPPORTED;
    }

    NTSTATUS handle_NtOpenProcess()
    {
        return STATUS_NOT_SUPPORTED;
    }

    NTSTATUS handle_NtOpenProcessToken(const syscall_context&, const handle process_handle, const ACCESS_MASK /*desired_access*/,
                                       const emulator_object<handle> token_handle)
    {
        if (process_handle != CURRENT_PROCESS)
        {
            return STATUS_NOT_SUPPORTED;
        }

        token_handle.write(CURRENT_PROCESS_TOKEN);

        return STATUS_SUCCESS;
    }

    NTSTATUS handle_NtOpenProcessTokenEx(const syscall_context& c, const handle process_handle, const ACCESS_MASK desired_access,
                                         const ULONG /*handle_attributes*/, const emulator_object<handle> token_handle)
    {
        return handle_NtOpenProcessToken(c, process_handle, desired_access, token_handle);
    }

    NTSTATUS handle_NtTerminateProcess(const syscall_context& c, const handle process_handle, NTSTATUS exit_status)
    {
        if (process_handle == 0)
        {
            for (auto& thread : c.proc.threads | std::views::values)
            {
                if (&thread != c.proc.active_thread)
                {
                    thread.exit_status = exit_status;
                }
            }

            return STATUS_SUCCESS;
        }

        if (process_handle == CURRENT_PROCESS)
        {
            c.proc.exit_status = exit_status;
            c.emu.stop();
            return STATUS_SUCCESS;
        }

        return STATUS_NOT_SUPPORTED;
    }
}
