// relay_control.cpp
#include <android/log.h>
#include <fcntl.h>
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/wait.h>

// Custom log macro
#define LOG_TAG "RelayControl"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

// Constants
#define STRELAY_BASE_PATH "/sys/class/strelay"
#define MODE_PATH "/sys/class/strelay/mode"
#define MAX_PATH_LENGTH 256
#define MAX_BUFFER_SIZE 64

// Function declarations
int detectRelayCount();
int writeToFile(const char* filePath, const char* content);
int writeToFileWithSystem(const char* filePath, const char* content);
int readFromFile(const char* filePath, char* buffer, size_t bufferSize);
bool fileExists(const char* filePath);
int fixRelayPermissions();
int executeRootCommand(const char* command);

// Helper function to check if file exists
bool fileExists(const char* filePath) {
    struct stat buffer;
    return (stat(filePath, &buffer) == 0);
}

// Function to detect number of relays
int detectRelayCount() {
    int count = 0;
    char relayPath[MAX_PATH_LENGTH];
    
    LOGD("Detecting relay count...");
    
    // Check if base directory exists
    if (!fileExists(STRELAY_BASE_PATH)) {
        LOGE("Base path %s does not exist", STRELAY_BASE_PATH);
        return 0;
    }
    
    // Check for relay files from 1 to 32
    for (int i = 1; i <= 32; i++) {
        snprintf(relayPath, sizeof(relayPath), "%s/relay%d", STRELAY_BASE_PATH, i);
        if (fileExists(relayPath)) {
            count = i;
            LOGD("Found relay%d", i);
        } else {
            break;
        }
    }
    
    // Ensure minimum of 2 relays for backward compatibility
    if (count < 2) {
        count = 2;
    }
    
    LOGI("Detected %d relays", count);
    return count;
}

// Function to write to file
int writeToFile(const char* filePath, const char* content) {
    LOGD("Attempting to write '%s' to %s", content, filePath);
    
    // Check file permissions first
    struct stat fileStat;
    if (stat(filePath, &fileStat) == 0) {
        LOGD("File permissions for %s: %o", filePath, fileStat.st_mode & 0777);
        LOGD("File owner: %d, group: %d", fileStat.st_uid, fileStat.st_gid);
    } else {
        LOGE("Failed to stat %s: %s", filePath, strerror(errno));
    }
    
    // Try different open modes
    int fd = open(filePath, O_WRONLY);
    if (fd < 0) {
        LOGE("Failed to open %s with O_WRONLY: %s (errno: %d)", filePath, strerror(errno), errno);
        
        // Try with O_WRONLY | O_TRUNC
        fd = open(filePath, O_WRONLY | O_TRUNC);
        if (fd < 0) {
            LOGE("Failed to open %s with O_WRONLY|O_TRUNC: %s (errno: %d)", filePath, strerror(errno), errno);
            
            // Try with O_RDWR
            fd = open(filePath, O_RDWR);
            if (fd < 0) {
                LOGE("Failed to open %s with O_RDWR: %s (errno: %d)", filePath, strerror(errno), errno);
                return -1;
            } else {
                LOGD("Successfully opened %s with O_RDWR", filePath);
            }
        } else {
            LOGD("Successfully opened %s with O_WRONLY|O_TRUNC", filePath);
        }
    } else {
        LOGD("Successfully opened %s with O_WRONLY", filePath);
    }
    
    // Add newline to content (many sysfs files expect this)
    char contentWithNewline[16];
    snprintf(contentWithNewline, sizeof(contentWithNewline), "%s\n", content);
    
    ssize_t bytesWritten = write(fd, contentWithNewline, strlen(contentWithNewline));
    if (bytesWritten < 0) {
        LOGE("Failed to write to %s: %s (errno: %d)", filePath, strerror(errno), errno);
        close(fd);
        return -1;
    }
    
    LOGD("Wrote %zd bytes ('%s') to %s", bytesWritten, contentWithNewline, filePath);
    
    // Ensure data is written to disk
    if (fsync(fd) < 0) {
        LOGE("Failed to sync %s: %s (errno: %d)", filePath, strerror(errno), errno);
        close(fd);
        return -1;
    }
    
    close(fd);
    LOGI("Successfully wrote '%s' to %s", content, filePath);
    return 0;
}

// Fallback method using system command
int writeToFileWithSystem(const char* filePath, const char* content) {
    char command[512];
    snprintf(command, sizeof(command), "echo %s > %s", content, filePath);
    
    LOGD("Trying system command: %s", command);
    
    int result = system(command);
    if (result == 0) {
        LOGI("Successfully wrote '%s' to %s using system command", content, filePath);
        return 0;
    } else {
        LOGE("System command failed with result: %d", result);
        
        // Try with su
        snprintf(command, sizeof(command), "su -c 'echo %s > %s'", content, filePath);
        LOGD("Trying with su: %s", command);
        
        result = system(command);
        if (result == 0) {
            LOGI("Successfully wrote '%s' to %s using su command", content, filePath);
            return 0;
        } else {
            LOGE("Su command also failed with result: %d", result);
            return -1;
        }
    }
}

// Function to execute root command using proper su syntax
int executeRootCommand(const char* command) {
    char fullCommand[1024];
    snprintf(fullCommand, sizeof(fullCommand), "echo '%s' | su", command);
    
    LOGD("Executing root command: %s", command);
    
    int result = system(fullCommand);
    int exitCode = WEXITSTATUS(result);
    
    if (exitCode == 0) {
        LOGI("Root command executed successfully: %s", command);
        return 0;
    } else {
        LOGE("Root command failed with exit code %d: %s", exitCode, command);
        return -1;
    }
}

// Function to fix relay file permissions automatically
int fixRelayPermissions() {
    LOGI("Attempting to fix relay file permissions...");
    
    int successCount = 0;
    
    // First, fix the base directory permissions
    if (executeRootCommand("chmod 755 /sys/class/strelay") == 0) {
        LOGI("Fixed permissions for base directory");
        successCount++;
    }
    
    // Fix mode file permissions
    if (executeRootCommand("chmod 666 /sys/class/strelay/mode") == 0) {
        LOGI("Fixed permissions for mode file");
        successCount++;
    }
    
    // Dynamically detect and fix permissions for all relay files
    LOGI("Scanning for relay files to fix permissions...");
    
    for (int i = 1; i <= 32; i++) {
        char relayPath[MAX_PATH_LENGTH];
        char command[256];
        
        snprintf(relayPath, sizeof(relayPath), "%s/relay%d", STRELAY_BASE_PATH, i);
        
        if (fileExists(relayPath)) {
            LOGI("Found relay%d, fixing permissions...", i);
            
            snprintf(command, sizeof(command), "chmod 666 /sys/class/strelay/relay%d", i);
            
            if (executeRootCommand(command) == 0) {
                LOGI("Successfully fixed permissions for relay%d", i);
                successCount++;
            } else {
                LOGE("Failed to fix permissions for relay%d", i);
            }
        } else {
            // No more relay files found
            LOGD("No relay%d found, stopping scan", i);
            break;
        }
    }
    
    LOGI("Fixed permissions for %d relay-related files", successCount);
    
    // Return success if at least the base directory and one relay were fixed
    return successCount >= 2 ? 0 : -1;
}

// Function to read from file
int readFromFile(const char* filePath, char* buffer, size_t bufferSize) {
    int fd = open(filePath, O_RDONLY);
    if (fd < 0) {
        LOGE("Failed to open %s for reading: %s", filePath, strerror(errno));
        return -1;
    }
    
    ssize_t bytesRead = read(fd, buffer, bufferSize - 1);
    if (bytesRead < 0) {
        LOGE("Failed to read from %s: %s", filePath, strerror(errno));
        close(fd);
        return -1;
    }
    
    // Null terminate and remove newline
    buffer[bytesRead] = '\0';
    if (bytesRead > 0 && buffer[bytesRead - 1] == '\n') {
        buffer[bytesRead - 1] = '\0';
    }
    
    close(fd);
    LOGD("Successfully read '%s' from %s", buffer, filePath);
    return 0;
}

// JNI Functions for interacting with Java code

extern "C" JNIEXPORT jint JNICALL
Java_io_kiotplugins_relaycontrol_RelayControl_getRelayCountNative(JNIEnv* env, jobject thiz) {
    int count = detectRelayCount();
    LOGI("Native getRelayCount returning: %d", count);
    return count;
}

extern "C" JNIEXPORT jboolean JNICALL
Java_io_kiotplugins_relaycontrol_RelayControl_setRelayStateNative(JNIEnv* env, jobject thiz, jint relayNumber, jint state) {
    char relayPath[MAX_PATH_LENGTH];
    char stateStr[4];
    
    LOGD("Setting relay %d to state %d", relayNumber, state);
    
    // Validate state
    if (state != 0 && state != 1) {
        LOGE("Invalid state %d. Must be 0 or 1", state);
        return JNI_FALSE;
    }
    
    // Build relay path
    snprintf(relayPath, sizeof(relayPath), "%s/relay%d", STRELAY_BASE_PATH, relayNumber);
    
    // Check if relay file exists
    if (!fileExists(relayPath)) {
        LOGE("Relay file %s does not exist", relayPath);
        return JNI_FALSE;
    }
    
    // Convert state to string
    snprintf(stateStr, sizeof(stateStr), "%d", state);
    
    // Write to relay file
    if (writeToFile(relayPath, stateStr) < 0) {
        LOGD("Direct write failed, trying system command fallback");
        if (writeToFileWithSystem(relayPath, stateStr) < 0) {
            LOGE("Both direct write and system command failed");
            return JNI_FALSE;
        }
    }
    
    LOGI("Successfully set relay %d to state %d", relayNumber, state);
    return JNI_TRUE;
}

extern "C" JNIEXPORT jint JNICALL
Java_io_kiotplugins_relaycontrol_RelayControl_getRelayStateNative(JNIEnv* env, jobject thiz, jint relayNumber) {
    char relayPath[MAX_PATH_LENGTH];
    char buffer[MAX_BUFFER_SIZE];
    
    LOGD("Getting state for relay %d", relayNumber);
    
    // Build relay path
    snprintf(relayPath, sizeof(relayPath), "%s/relay%d", STRELAY_BASE_PATH, relayNumber);
    
    // Check if relay file exists
    if (!fileExists(relayPath)) {
        LOGE("Relay file %s does not exist", relayPath);
        return -1;
    }
    
    // Read from relay file
    if (readFromFile(relayPath, buffer, sizeof(buffer)) < 0) {
        return -1;
    }
    
    // Convert to integer
    int state = atoi(buffer);
    LOGD("Relay %d state: %d", relayNumber, state);
    return state;
}

extern "C" JNIEXPORT jboolean JNICALL
Java_io_kiotplugins_relaycontrol_RelayControl_setModeNative(JNIEnv* env, jobject thiz, jint mode) {
    char modeStr[16];
    
    LOGD("Setting mode to %d", mode);
    
    // Check if mode file exists
    if (!fileExists(MODE_PATH)) {
        LOGE("Mode file %s does not exist", MODE_PATH);
        return JNI_FALSE;
    }
    
    // Convert mode to string
    snprintf(modeStr, sizeof(modeStr), "%d", mode);
    
    // Write to mode file
    if (writeToFile(MODE_PATH, modeStr) < 0) {
        return JNI_FALSE;
    }
    
    LOGI("Successfully set mode to %d", mode);
    return JNI_TRUE;
}

extern "C" JNIEXPORT jint JNICALL
Java_io_kiotplugins_relaycontrol_RelayControl_getModeNative(JNIEnv* env, jobject thiz) {
    char buffer[MAX_BUFFER_SIZE];
    
    LOGD("Getting current mode");
    
    // Check if mode file exists
    if (!fileExists(MODE_PATH)) {
        LOGE("Mode file %s does not exist", MODE_PATH);
        return -1;
    }
    
    // Read from mode file
    if (readFromFile(MODE_PATH, buffer, sizeof(buffer)) < 0) {
        return -1;
    }
    
    // Convert to integer
    int mode = atoi(buffer);
    LOGD("Current mode: %d", mode);
    return mode;
}

extern "C" JNIEXPORT jboolean JNICALL
Java_io_kiotplugins_relaycontrol_RelayControl_checkRelayAccessNative(JNIEnv* env, jobject thiz) {
    LOGD("Checking relay system access");
    
    // Check if base directory exists
    if (!fileExists(STRELAY_BASE_PATH)) {
        LOGE("Relay system not available: %s does not exist", STRELAY_BASE_PATH);
        return JNI_FALSE;
    }
    
    // Try to read from relay1 to test access
    char relay1Path[MAX_PATH_LENGTH];
    snprintf(relay1Path, sizeof(relay1Path), "%s/relay1", STRELAY_BASE_PATH);
    
    if (!fileExists(relay1Path)) {
        LOGE("Relay1 file does not exist: %s", relay1Path);
        return JNI_FALSE;
    }
    
    // Try to read the current state
    char buffer[MAX_BUFFER_SIZE];
    if (readFromFile(relay1Path, buffer, sizeof(buffer)) < 0) {
        LOGE("Cannot read relay1 state - insufficient permissions");
        return JNI_FALSE;
    }
    
    LOGI("Relay system access verified");
    return JNI_TRUE;
}

extern "C" JNIEXPORT jboolean JNICALL
Java_io_kiotplugins_relaycontrol_RelayControl_fixPermissionsNative(JNIEnv* env, jobject thiz) {
    LOGI("Attempting to fix relay permissions automatically");
    
    if (fixRelayPermissions() == 0) {
        LOGI("Successfully fixed relay permissions");
        return JNI_TRUE;
    } else {
        LOGE("Failed to fix relay permissions");
        return JNI_FALSE;
    }
}