#undef UNICODE
#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <stdio.h>
#include "browser_info.h"
#include "paquete.h"
#include "analizador.h"
#include "debug.h"

#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "version.lib")

// Maximum path length
#define MAX_PATH_LEN 512

/**
 * @brief Check if a file exists at the given path
 */
BOOL FileExists(LPCSTR path) {
    DWORD dwAttrib = GetFileAttributesA(path);
    return (dwAttrib != INVALID_FILE_ATTRIBUTES && 
            !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
}

/**
 * @brief Get file version information from an executable
 */
BOOL GetFileVersion(LPCSTR filePath, char* version, SIZE_T versionSize) {
    DWORD dwHandle = 0;
    DWORD dwSize = GetFileVersionInfoSizeA(filePath, &dwHandle);
    
    if (dwSize == 0) {
        return FALSE;
    }
    
    PBYTE pVersionInfo = (PBYTE)LocalAlloc(LPTR, dwSize);
    if (!pVersionInfo) {
        return FALSE;
    }
    
    if (!GetFileVersionInfoA(filePath, dwHandle, dwSize, pVersionInfo)) {
        LocalFree(pVersionInfo);
        return FALSE;
    }
    
    VS_FIXEDFILEINFO* pFileInfo = NULL;
    UINT uLen = 0;
    
    if (VerQueryValueA(pVersionInfo, "\\", (LPVOID*)&pFileInfo, &uLen)) {
        DWORD dwFileVersionMS = pFileInfo->dwFileVersionMS;
        DWORD dwFileVersionLS = pFileInfo->dwFileVersionLS;
        
        DWORD major = HIWORD(dwFileVersionMS);
        DWORD minor = LOWORD(dwFileVersionMS);
        DWORD build = HIWORD(dwFileVersionLS);
        DWORD revision = LOWORD(dwFileVersionLS);
        
        snprintf(version, versionSize, "%lu.%lu.%lu.%lu", 
                 major, minor, build, revision);
        
        LocalFree(pVersionInfo);
        return TRUE;
    }
    
    LocalFree(pVersionInfo);
    return FALSE;
}

/**
 * @brief Check for Chrome installation
 */
BOOL CheckChrome(char* path, SIZE_T pathSize, char* version, SIZE_T versionSize) {
    // Common Chrome installation paths
    const char* chromePaths[] = {
        "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
        "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
        "%LOCALAPPDATA%\\Google\\Chrome\\Application\\chrome.exe",
        NULL
    };
    
    char expandedPath[MAX_PATH_LEN] = {0};
    
    for (int i = 0; chromePaths[i] != NULL; i++) {
        if (ExpandEnvironmentStringsA(chromePaths[i], expandedPath, MAX_PATH_LEN) > 0) {
            if (FileExists(expandedPath)) {
                strncpy_s(path, pathSize, expandedPath, _TRUNCATE);
                GetFileVersion(expandedPath, version, versionSize);
                return TRUE;
            }
        } else {
            // Try direct path if expansion fails
            if (FileExists(chromePaths[i])) {
                strncpy_s(path, pathSize, chromePaths[i], _TRUNCATE);
                GetFileVersion(chromePaths[i], version, versionSize);
                return TRUE;
            }
        }
    }
    
    return FALSE;
}

/**
 * @brief Check for Edge installation
 */
BOOL CheckEdge(char* path, SIZE_T pathSize, char* version, SIZE_T versionSize) {
    // Common Edge installation paths
    const char* edgePaths[] = {
        "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
        "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
        "%LOCALAPPDATA%\\Microsoft\\Edge\\Application\\msedge.exe",
        NULL
    };
    
    char expandedPath[MAX_PATH_LEN] = {0};
    
    for (int i = 0; edgePaths[i] != NULL; i++) {
        if (ExpandEnvironmentStringsA(edgePaths[i], expandedPath, MAX_PATH_LEN) > 0) {
            if (FileExists(expandedPath)) {
                strncpy_s(path, pathSize, expandedPath, _TRUNCATE);
                GetFileVersion(expandedPath, version, versionSize);
                return TRUE;
            }
        } else {
            if (FileExists(edgePaths[i])) {
                strncpy_s(path, pathSize, edgePaths[i], _TRUNCATE);
                GetFileVersion(edgePaths[i], version, versionSize);
                return TRUE;
            }
        }
    }
    
    return FALSE;
}

/**
 * @brief Check for Firefox installation
 */
BOOL CheckFirefox(char* path, SIZE_T pathSize, char* version, SIZE_T versionSize) {
    // Common Firefox installation paths
    const char* firefoxPaths[] = {
        "C:\\Program Files\\Mozilla Firefox\\firefox.exe",
        "C:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe",
        "%APPDATA%\\Mozilla\\Firefox\\firefox.exe",
        NULL
    };
    
    char expandedPath[MAX_PATH_LEN] = {0};
    
    for (int i = 0; firefoxPaths[i] != NULL; i++) {
        if (ExpandEnvironmentStringsA(firefoxPaths[i], expandedPath, MAX_PATH_LEN) > 0) {
            if (FileExists(expandedPath)) {
                strncpy_s(path, pathSize, expandedPath, _TRUNCATE);
                GetFileVersion(expandedPath, version, versionSize);
                return TRUE;
            }
        } else {
            if (FileExists(firefoxPaths[i])) {
                strncpy_s(path, pathSize, firefoxPaths[i], _TRUNCATE);
                GetFileVersion(firefoxPaths[i], version, versionSize);
                return TRUE;
            }
        }
    }
    
    return FALSE;
}

/**
 * @brief Check for Opera installation
 */
BOOL CheckOpera(char* path, SIZE_T pathSize, char* version, SIZE_T versionSize) {
    // Common Opera installation paths
    const char* operaPaths[] = {
        "C:\\Program Files\\Opera\\opera.exe",
        "C:\\Program Files (x86)\\Opera\\opera.exe",
        "%LOCALAPPDATA%\\Programs\\Opera\\opera.exe",
        NULL
    };
    
    char expandedPath[MAX_PATH_LEN] = {0};
    
    for (int i = 0; operaPaths[i] != NULL; i++) {
        if (ExpandEnvironmentStringsA(operaPaths[i], expandedPath, MAX_PATH_LEN) > 0) {
            if (FileExists(expandedPath)) {
                strncpy_s(path, pathSize, expandedPath, _TRUNCATE);
                GetFileVersion(expandedPath, version, versionSize);
                return TRUE;
            }
        } else {
            if (FileExists(operaPaths[i])) {
                strncpy_s(path, pathSize, operaPaths[i], _TRUNCATE);
                GetFileVersion(operaPaths[i], version, versionSize);
                return TRUE;
            }
        }
    }
    
    return FALSE;
}

/**
 * @brief Check for Brave installation
 */
BOOL CheckBrave(char* path, SIZE_T pathSize, char* version, SIZE_T versionSize) {
    // Common Brave installation paths
    const char* bravePaths[] = {
        "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
        "C:\\Program Files (x86)\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
        "%LOCALAPPDATA%\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
        NULL
    };
    
    char expandedPath[MAX_PATH_LEN] = {0};
    
    for (int i = 0; bravePaths[i] != NULL; i++) {
        if (ExpandEnvironmentStringsA(bravePaths[i], expandedPath, MAX_PATH_LEN) > 0) {
            if (FileExists(expandedPath)) {
                strncpy_s(path, pathSize, expandedPath, _TRUNCATE);
                GetFileVersion(expandedPath, version, versionSize);
                return TRUE;
            }
        } else {
            if (FileExists(bravePaths[i])) {
                strncpy_s(path, pathSize, bravePaths[i], _TRUNCATE);
                GetFileVersion(bravePaths[i], version, versionSize);
                return TRUE;
            }
        }
    }
    
    return FALSE;
}

/**
 * @brief Get default browser from registry
 */
BOOL GetDefaultBrowser(char* browserName, SIZE_T nameSize, char* browserPath, SIZE_T pathSize) {
    HKEY hKey = NULL;
    char progId[256] = {0};
    DWORD progIdSize = sizeof(progId);
    char httpProgId[256] = {0};
    DWORD httpProgIdSize = sizeof(httpProgId);
    
    // Try to get default browser from HTTP protocol association
    if (RegOpenKeyExA(HKEY_CURRENT_USER, 
                     "Software\\Microsoft\\Windows\\Shell\\Associations\\URLAssociations\\http\\UserChoice",
                     0, KEY_READ, &hKey) == ERROR_SUCCESS) {
        if (RegQueryValueExA(hKey, "ProgId", NULL, NULL, (LPBYTE)httpProgId, &httpProgIdSize) == ERROR_SUCCESS) {
            RegCloseKey(hKey);
            
            // Map ProgId to browser name
            if (strstr(httpProgId, "ChromeHTML") != NULL) {
                strncpy_s(browserName, nameSize, "Google Chrome", _TRUNCATE);
                CheckChrome(browserPath, pathSize, NULL, 0);
                return TRUE;
            } else if (strstr(httpProgId, "MSEdgeHTM") != NULL || strstr(httpProgId, "AppX") != NULL) {
                strncpy_s(browserName, nameSize, "Microsoft Edge", _TRUNCATE);
                CheckEdge(browserPath, pathSize, NULL, 0);
                return TRUE;
            } else if (strstr(httpProgId, "FirefoxURL") != NULL) {
                strncpy_s(browserName, nameSize, "Mozilla Firefox", _TRUNCATE);
                CheckFirefox(browserPath, pathSize, NULL, 0);
                return TRUE;
            } else if (strstr(httpProgId, "OperaStable") != NULL) {
                strncpy_s(browserName, nameSize, "Opera", _TRUNCATE);
                CheckOpera(browserPath, pathSize, NULL, 0);
                return TRUE;
            } else {
                strncpy_s(browserName, nameSize, httpProgId, _TRUNCATE);
                return TRUE;
            }
        }
        RegCloseKey(hKey);
    }
    
    // Fallback: try to get from HKEY_CLASSES_ROOT\http\shell\open\command
    if (RegOpenKeyExA(HKEY_CLASSES_ROOT, "http\\shell\\open\\command", 0, KEY_READ, &hKey) == ERROR_SUCCESS) {
        char command[MAX_PATH_LEN] = {0};
        DWORD commandSize = sizeof(command);
        
        if (RegQueryValueExA(hKey, NULL, NULL, NULL, (LPBYTE)command, &commandSize) == ERROR_SUCCESS) {
            RegCloseKey(hKey);
            
            // Extract executable path (remove quotes and parameters)
            char* exePath = command;
            if (command[0] == '"') {
                exePath = command + 1;
                char* endQuote = strchr(exePath, '"');
                if (endQuote) {
                    *endQuote = '\0';
                }
            } else {
                // Find first space (end of path)
                char* space = strchr(exePath, ' ');
                if (space) {
                    *space = '\0';
                }
            }
            
            if (FileExists(exePath)) {
                strncpy_s(browserPath, pathSize, exePath, _TRUNCATE);
                
                // Try to identify browser from path
                if (strstr(exePath, "chrome.exe") != NULL) {
                    strncpy_s(browserName, nameSize, "Google Chrome", _TRUNCATE);
                } else if (strstr(exePath, "msedge.exe") != NULL) {
                    strncpy_s(browserName, nameSize, "Microsoft Edge", _TRUNCATE);
                } else if (strstr(exePath, "firefox.exe") != NULL) {
                    strncpy_s(browserName, nameSize, "Mozilla Firefox", _TRUNCATE);
                } else if (strstr(exePath, "opera.exe") != NULL) {
                    strncpy_s(browserName, nameSize, "Opera", _TRUNCATE);
                } else if (strstr(exePath, "brave.exe") != NULL) {
                    strncpy_s(browserName, nameSize, "Brave", _TRUNCATE);
                } else {
                    // Extract filename
                    char* filename = strrchr(exePath, '\\');
                    if (filename) {
                        filename++;
                    } else {
                        filename = exePath;
                    }
                    strncpy_s(browserName, nameSize, filename, _TRUNCATE);
                }
                
                return TRUE;
            }
        } else {
            RegCloseKey(hKey);
        }
    }
    
    return FALSE;
}

/**
 * @brief Identify installed browsers and default browser
 * Lists all detected browsers and identifies the system default browser
 */
VOID IdentificarNavegadores(PAnalizador argumentos) {
    SIZE_T tamanoUuid = 36;
    UINT32 nbArg = getInt32(argumentos);
    PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
    
    PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE);
    addString(respuesta, tareaUuid, FALSE);
    PPaquete salida = nuevoPaquete(0, FALSE);
    
    PackageAddFormatPrintf(salida, FALSE, "=== Installed Browsers ===\n\n");
    
    int browserCount = 0;
    char path[MAX_PATH_LEN] = {0};
    char version[64] = {0};
    
    // Check Chrome
    if (CheckChrome(path, sizeof(path), version, sizeof(version))) {
        PackageAddFormatPrintf(salida, FALSE, "[%d] Google Chrome\n", ++browserCount);
        PackageAddFormatPrintf(salida, FALSE, "    Path: %s\n", path);
        if (strlen(version) > 0) {
            PackageAddFormatPrintf(salida, FALSE, "    Version: %s\n", version);
        }
        PackageAddFormatPrintf(salida, FALSE, "\n");
    }
    
    // Check Edge
    if (CheckEdge(path, sizeof(path), version, sizeof(version))) {
        PackageAddFormatPrintf(salida, FALSE, "[%d] Microsoft Edge\n", ++browserCount);
        PackageAddFormatPrintf(salida, FALSE, "    Path: %s\n", path);
        if (strlen(version) > 0) {
            PackageAddFormatPrintf(salida, FALSE, "    Version: %s\n", version);
        }
        PackageAddFormatPrintf(salida, FALSE, "\n");
    }
    
    // Check Firefox
    if (CheckFirefox(path, sizeof(path), version, sizeof(version))) {
        PackageAddFormatPrintf(salida, FALSE, "[%d] Mozilla Firefox\n", ++browserCount);
        PackageAddFormatPrintf(salida, FALSE, "    Path: %s\n", path);
        if (strlen(version) > 0) {
            PackageAddFormatPrintf(salida, FALSE, "    Version: %s\n", version);
        }
        PackageAddFormatPrintf(salida, FALSE, "\n");
    }
    
    // Check Opera
    if (CheckOpera(path, sizeof(path), version, sizeof(version))) {
        PackageAddFormatPrintf(salida, FALSE, "[%d] Opera\n", ++browserCount);
        PackageAddFormatPrintf(salida, FALSE, "    Path: %s\n", path);
        if (strlen(version) > 0) {
            PackageAddFormatPrintf(salida, FALSE, "    Version: %s\n", version);
        }
        PackageAddFormatPrintf(salida, FALSE, "\n");
    }
    
    // Check Brave
    if (CheckBrave(path, sizeof(path), version, sizeof(version))) {
        PackageAddFormatPrintf(salida, FALSE, "[%d] Brave\n", ++browserCount);
        PackageAddFormatPrintf(salida, FALSE, "    Path: %s\n", path);
        if (strlen(version) > 0) {
            PackageAddFormatPrintf(salida, FALSE, "    Version: %s\n", version);
        }
        PackageAddFormatPrintf(salida, FALSE, "\n");
    }
    
    if (browserCount == 0) {
        PackageAddFormatPrintf(salida, FALSE, "No browsers detected.\n\n");
    }
    
    // Get default browser
    PackageAddFormatPrintf(salida, FALSE, "=== Default Browser ===\n\n");
    char defaultBrowser[256] = {0};
    char defaultPath[MAX_PATH_LEN] = {0};
    
    if (GetDefaultBrowser(defaultBrowser, sizeof(defaultBrowser), 
                         defaultPath, sizeof(defaultPath))) {
        PackageAddFormatPrintf(salida, FALSE, "Name: %s\n", defaultBrowser);
        if (strlen(defaultPath) > 0) {
            PackageAddFormatPrintf(salida, FALSE, "Path: %s\n", defaultPath);
        }
    } else {
        PackageAddFormatPrintf(salida, FALSE, "Could not determine default browser.\n");
    }
    
    // Add output first
    addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
    
    // Add artifacts for registry reads (after output)
    addArtifact(respuesta, "Registry Read", "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\URLAssociations\\http\\UserChoice");
    addArtifact(respuesta, "Registry Read", "HKEY_CLASSES_ROOT\\http\\shell\\open\\command");
    mandarPaquete(respuesta);
    liberarPaquete(salida);
    liberarPaquete(respuesta);
}

