/*
 * File Manager - File I/O operations
 * Author: Roman Necas (xnecasr00)
 * Date: 13. October 2025
 *
 * This module provides secure file management operations including:
 * - File reading and writing with chunk-based I/O
 * - Filename validation and path traversal protection
 * - Atomic file operations with temporary files
 * - File transfer statistics tracking
 */

#include "secret.h"

/* Initialize file manager for reading:
 * - Validates inputs
 * - Opens file in binary read mode
 * - Determines file size and number of fragments
 */
int file_manager_open_read(file_manager_t *fm, const char *filename) {
    // Validate input pointers
    if (!fm || !filename) {
        print_error("Invalid parameters for file manager read initialization");
        return -1;
    }

    memset(fm, 0, sizeof(file_manager_t));

    fm->filename = strdup(filename);
    if (!fm->filename) {
        print_error("Memory allocation failed");
        return -1;
    }

    fm->file_handle = fopen(filename, "rb");
    if (!fm->file_handle) {
        fprintf(stderr, "Error opening file '%s': %s\n", filename, strerror(errno));
        free(fm->filename);
        return -1;
    }

    if (fseek(fm->file_handle, 0, SEEK_END) != 0) {
        fprintf(stderr, "Error seeking to end of file: %s\n", strerror(errno));
        fclose(fm->file_handle);
        free(fm->filename);
        return -1;
    }

    long file_size = ftell(fm->file_handle);
    if (file_size < 0) {
        fprintf(stderr, "Error getting file size: %s\n", strerror(errno));
        fclose(fm->file_handle);
        free(fm->filename);
        return -1;
    }

    fm->file_size = (uint64_t)file_size;

    if (fseek(fm->file_handle, 0, SEEK_SET) != 0) {
        fprintf(stderr, "Error seeking to beginning of file: %s\n", strerror(errno));
        fclose(fm->file_handle);
        free(fm->filename);
        return -1;
    }

    fm->total_fragments = (fm->file_size + MAX_PAYLOAD_SIZE - 1) / MAX_PAYLOAD_SIZE;
    if (fm->total_fragments == 0) {
        fm->total_fragments = 1;
    }

    return 0;
}

/* Initialize file manager for writing:
 * - Validates inputs
 * - Creates/opens file in binary write mode
 */
int file_manager_open_write(file_manager_t *fm, const char *filename) {
    // Validate input pointers and allocate filename
    if (!fm || !filename) {
        print_error("Invalid parameters for file manager write initialization");
        return -1;
    }

    memset(fm, 0, sizeof(file_manager_t));

    fm->filename = strdup(filename);
    if (!fm->filename) {
        print_error("Memory allocation failed");
        return -1;
    }

    fm->file_handle = fopen(filename, "wb");
    if (!fm->file_handle) {
        fprintf(stderr, "Error creating file '%s': %s\n", filename, strerror(errno));
        free(fm->filename);
        return -1;
    }

    return 0;
}

/* Read a chunk from the open file:
 * - Reads up to chunk_size but not beyond file_size
 * - Updates bytes_processed and fragment counter
 * - Returns: 1 = chunk read, 0 = EOF, -1 = error
 */
int file_manager_read_chunk(file_manager_t *fm, void *buffer, size_t chunk_size, size_t *bytes_read) {
    // Parameter and state checks
    if (!fm || !buffer || !bytes_read || !fm->file_handle) {
        print_error("Invalid parameters for file chunk reading");
        return -1;
    }

    *bytes_read = 0;

    if (fm->bytes_processed >= fm->file_size) {
        return 0;
    }

    size_t to_read = chunk_size;
    if (fm->bytes_processed + to_read > fm->file_size) {
        to_read = fm->file_size - fm->bytes_processed;
    }

    size_t actually_read = fread(buffer, 1, to_read, fm->file_handle);
    if (actually_read == 0) {
        if (feof(fm->file_handle)) {
            return 0;
        }
        fprintf(stderr, "Error reading from file: %s\n", strerror(errno));
        return -1;
    }

    fm->bytes_processed += actually_read;
    fm->current_fragment++;
    *bytes_read = actually_read;

    return 1;
}

/* Write a chunk to the open file:
 * - Writes bytes_to_write and flushes to reduce data loss risk
 * - Updates bytes_processed
 */
int file_manager_write_chunk(file_manager_t *fm, const void *buffer, size_t bytes_to_write) {
    // Parameter checks and write handling
    if (!fm || !buffer || !fm->file_handle) {
        print_error("Invalid parameters for file chunk writing");
        return -1;
    }

    if (bytes_to_write == 0) {
        return 0;
    }

    size_t written = fwrite(buffer, 1, bytes_to_write, fm->file_handle);
    if (written != bytes_to_write) {
        fprintf(stderr, "Error writing to file: %s\n", strerror(errno));
        return -1;
    }

    fm->bytes_processed += written;

    if (fflush(fm->file_handle) != 0) {
        fprintf(stderr, "Error flushing file: %s\n", strerror(errno));
        return -1;
    }

    return 0;
}

/* Seek to a specific file position:
 * - Uses fseek; caller must ensure position is valid
 */
int file_manager_seek_to_position(file_manager_t *fm, uint64_t position) {
    if (!fm || !fm->file_handle) {
        print_error("Invalid parameters for file seeking");
        return -1;
    }

    if (fseek(fm->file_handle, (long)position, SEEK_SET) != 0) {
        fprintf(stderr, "Error seeking to position %lu: %s\n", position, strerror(errno));
        return -1;
    }

    return 0;
}

/* Return current and total fragment counts */
int file_manager_get_fragment_info(const file_manager_t *fm, uint32_t *current, uint32_t *total) {
    if (!fm || !current || !total) {
        print_error("Invalid parameters for fragment info");
        return -1;
    }

    *current = fm->current_fragment;
    *total = fm->total_fragments;
    return 0;
}

/* Simple getters for file metadata */
uint64_t file_manager_get_file_size(const file_manager_t *fm) {
    if (!fm) {
        return 0;
    }
    return fm->file_size;
}

uint64_t file_manager_get_bytes_processed(const file_manager_t *fm) {
    if (!fm) {
        return 0;
    }
    return fm->bytes_processed;
}

const char *file_manager_get_filename(const file_manager_t *fm) {
    if (!fm) {
        return NULL;
    }
    return fm->filename;
}

/* Check if file transfer/read is complete */
int file_manager_is_complete(const file_manager_t *fm) {
    if (!fm) {
        return 0;
    }
    return fm->bytes_processed >= fm->file_size;
}

/* Cleanup: close file handle and free filename buffer */
void file_manager_cleanup(file_manager_t *fm) {
    if (fm) {
        if (fm->file_handle) {
            fclose(fm->file_handle);
            fm->file_handle = NULL;
        }
        if (fm->filename) {
            free(fm->filename);
            fm->filename = NULL;
        }
        memset(fm, 0, sizeof(file_manager_t));
    }
}

/* Manually set file size and recalc fragment count */
void file_manager_set_file_size(file_manager_t *fm, uint64_t file_size) {
    if (fm) {
        fm->file_size = file_size;
        fm->total_fragments = (file_size + MAX_PAYLOAD_SIZE - 1) / MAX_PAYLOAD_SIZE;
        if (fm->total_fragments == 0) {
            fm->total_fragments = 1;
        }
    }
}

/* Extract basename from a path (safe copy) */
int file_manager_get_basename(const char *path, char *basename, size_t basename_len) {
    if (!path || !basename || basename_len == 0) {
        return -1;
    }

    const char *last_slash = strrchr(path, '/');
    const char *filename = last_slash ? last_slash + 1 : path;

    size_t filename_len = strlen(filename);
    if (filename_len >= basename_len) {
        return -1;
    }

    strcpy(basename, filename);
    return 0;
}

/* Enhanced File I/O and Performance Features */

/* Check for file existence using access() */
int file_exists(const char *filename) {
    // Validate args and check F_OK
    ERROR_CHECK(filename != NULL, ERR_INVALID_ARGS, "Filename cannot be NULL");

    return access(filename, F_OK) == 0 ? 1 : 0;
}

/* Check readability using access() */
int file_is_readable(const char *filename) {
    ERROR_CHECK(filename != NULL, ERR_INVALID_ARGS, "Filename cannot be NULL");

    return access(filename, R_OK) == 0 ? 1 : 0;
}

/* Check writability using access() */
int file_is_writable(const char *filename) {
    ERROR_CHECK(filename != NULL, ERR_INVALID_ARGS, "Filename cannot be NULL");

    return access(filename, W_OK) == 0 ? 1 : 0;
}

/* Return file size via stat() */
uint64_t file_get_size(const char *filename) {
    ERROR_CHECK(filename != NULL, ERR_INVALID_ARGS, "Filename cannot be NULL");

    struct stat file_stat;
    if (stat(filename, &file_stat) != 0) {
        ERROR_SET(ERR_FILE_ACCESS, "Cannot stat file");
        return 0;
    }

    return (uint64_t)file_stat.st_size;
}

/* Get file permissions (mode_t) */
int file_get_permissions(const char *filename, mode_t *permissions) {
    ERROR_CHECK(filename != NULL, ERR_INVALID_ARGS, "Filename cannot be NULL");
    ERROR_CHECK(permissions != NULL, ERR_INVALID_ARGS, "Permissions pointer cannot be NULL");

    struct stat file_stat;
    if (stat(filename, &file_stat) != 0) {
        ERROR_SET(ERR_FILE_ACCESS, "Cannot stat file");
        return -1;
    }

    *permissions = file_stat.st_mode;
    return 0;
}

/* Set file permissions using chmod() */
int file_set_permissions(const char *filename, mode_t permissions) {
    ERROR_CHECK(filename != NULL, ERR_INVALID_ARGS, "Filename cannot be NULL");

    if (chmod(filename, permissions) != 0) {
        ERROR_SET(ERR_FILE_ACCESS, "Cannot set file permissions");
        return -1;
    }

    return 0;
}

/* Get file modification time via stat() */
time_t file_get_modification_time(const char *filename) {
    ERROR_CHECK(filename != NULL, ERR_INVALID_ARGS, "Filename cannot be NULL");

    struct stat file_stat;
    if (stat(filename, &file_stat) != 0) {
        ERROR_SET(ERR_FILE_ACCESS, "Cannot stat file");
        return 0;
    }

    return file_stat.st_mtime;
}

/* Safe file operations */

/* Create a secure temporary file using mkstemp.
 * - template_path must be writable by caller and large enough
 * - Returns an open file descriptor on success
 */
int file_create_temp(char *template_path, size_t template_size) {
    ERROR_CHECK(template_path != NULL, ERR_INVALID_ARGS, "Template path cannot be NULL");
    ERROR_CHECK(template_size > 0, ERR_INVALID_ARGS, "Template size must be positive");

    strncpy(template_path, "/tmp/secret_XXXXXX", template_size - 1);
    template_path[template_size - 1] = '\0';

    int fd = mkstemp(template_path);
    if (fd == -1) {
        ERROR_SET(ERR_FILE_ACCESS, "Cannot create temporary file");
        return -1;
    }

    return fd;
}

/* Perform atomic rename (temp -> final) using rename().
 * - Relies on filesystem atomicity where supported
 */
int file_atomic_rename(const char *temp_filename, const char *final_filename) {
    ERROR_CHECK(temp_filename != NULL, ERR_INVALID_ARGS, "Temp filename cannot be NULL");
    ERROR_CHECK(final_filename != NULL, ERR_INVALID_ARGS, "Final filename cannot be NULL");

    if (rename(temp_filename, final_filename) != 0) {
        ERROR_SET(ERR_FILE_ACCESS, "Cannot rename file atomically");
        return -1;
    }

    return 0;
}

/* Securely delete a file by overwriting with random data and unlinking:
 * - Opens file for read/write, overwrites in blocks, flushes and fsyncs
 * - Uses crypto_random_bytes() if available, falls back to rand()
 * - Note: cannot guarantee secure erase on journaling or SSD devices
 */
int file_secure_delete(const char *filename) {
    ERROR_CHECK(filename != NULL, ERR_INVALID_ARGS, "Filename cannot be NULL");

    struct stat file_stat;
    if (stat(filename, &file_stat) != 0) {
        ERROR_SET(ERR_FILE_ACCESS, "Cannot stat file for secure deletion");
        return -1;
    }

    /* Open file for writing */
    FILE *file = fopen(filename, "r+b");
    if (!file) {
        ERROR_SET(ERR_FILE_ACCESS, "Cannot open file for secure deletion");
        return -1;
    }

    /* Overwrite with random data */
    uint64_t file_size = (uint64_t)file_stat.st_size;
    uint8_t *random_buffer = malloc(4096);
    if (!random_buffer) {
        fclose(file);
        ERROR_SET(ERR_MEMORY_ALLOC, "Cannot allocate buffer for secure deletion");
        return -1;
    }

    for (uint64_t offset = 0; offset < file_size; offset += 4096) {
        size_t to_write = (file_size - offset < 4096) ? (file_size - offset) : 4096;

        /* Fill with random data */
        if (crypto_random_bytes(random_buffer, to_write) != 0) {
            /* Fallback to pseudo-random */
            for (size_t i = 0; i < to_write; i++) {
                random_buffer[i] = (uint8_t)rand();
            }
        }

        if (fwrite(random_buffer, 1, to_write, file) != to_write) {
            ERROR_SET(ERR_FILE_WRITE, "Cannot overwrite file for secure deletion");
            free(random_buffer);
            fclose(file);
            return -1;
        }
    }

    fflush(file);
    fsync(fileno(file));
    fclose(file);
    free(random_buffer);

    /* Finally remove the file */
    if (unlink(filename) != 0) {
        ERROR_SET(ERR_FILE_ACCESS, "Cannot unlink file after secure deletion");
        return -1;
    }

    return 0;
}

/* Create a timestamped backup copy of filename:
 * - Writes to filename.backup_YYYYMMDD_HHMMSS
 * - Copies file in chunks to preserve memory
 */
int file_create_backup(const char *filename, char *backup_filename, size_t backup_size) {
    ERROR_CHECK(filename != NULL, ERR_INVALID_ARGS, "Filename cannot be NULL");
    ERROR_CHECK(backup_filename != NULL, ERR_INVALID_ARGS, "Backup filename buffer cannot be NULL");
    ERROR_CHECK(backup_size > 0, ERR_INVALID_ARGS, "Backup filename buffer size must be positive");

    time_t now = time(NULL);
    struct tm *tm_info = localtime(&now);

    snprintf(backup_filename, backup_size, "%s.backup_%04d%02d%02d_%02d%02d%02d",
             filename, tm_info->tm_year + 1900, tm_info->tm_mon + 1, tm_info->tm_mday,
             tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec);

    /* Copy file */
    FILE *src = fopen(filename, "rb");
    if (!src) {
        ERROR_SET(ERR_FILE_ACCESS, "Cannot open source file for backup");
        return -1;
    }

    FILE *dst = fopen(backup_filename, "wb");
    if (!dst) {
        fclose(src);
        ERROR_SET(ERR_FILE_ACCESS, "Cannot create backup file");
        return -1;
    }

    char buffer[8192];
    size_t bytes_read;
    while ((bytes_read = fread(buffer, 1, sizeof(buffer), src)) > 0) {
        if (fwrite(buffer, 1, bytes_read, dst) != bytes_read) {
            fclose(src);
            fclose(dst);
            unlink(backup_filename);
            ERROR_SET(ERR_FILE_WRITE, "Cannot write to backup file");
            return -1;
        }
    }

    fclose(src);
    fclose(dst);

    return 0;
}

/* Path manipulation functions */

/* Normalize path using realpath() when possible.
 * - Falls back to copying input if realpath fails
 */
int path_normalize(const char *input_path, char *output_path, size_t output_size) {
    ERROR_CHECK(input_path != NULL, ERR_INVALID_ARGS, "Input path cannot be NULL");
    ERROR_CHECK(output_path != NULL, ERR_INVALID_ARGS, "Output path buffer cannot be NULL");
    ERROR_CHECK(output_size > 0, ERR_INVALID_ARGS, "Output buffer size must be positive");

    /* Simple path normalization - resolve relative paths */
    if (realpath(input_path, output_path) == NULL) {
        /* If realpath fails, just copy the input */
        strncpy(output_path, input_path, output_size - 1);
        output_path[output_size - 1] = '\0';
    }

    return 0;
}

/* Join directory and filename safely, preventing overflow */
int path_join(const char *dir, const char *filename, char *output_path, size_t output_size) {
    ERROR_CHECK(dir != NULL, ERR_INVALID_ARGS, "Directory cannot be NULL");
    ERROR_CHECK(filename != NULL, ERR_INVALID_ARGS, "Filename cannot be NULL");
    ERROR_CHECK(output_path != NULL, ERR_INVALID_ARGS, "Output path buffer cannot be NULL");

    size_t dir_len = strlen(dir);
    int needs_separator = (dir_len > 0 && dir[dir_len - 1] != '/');

    int result = snprintf(output_path, output_size, "%s%s%s",
                         dir, needs_separator ? "/" : "", filename);

    if (result < 0 || (size_t)result >= output_size) {
        ERROR_SET(ERR_INVALID_ARGS, "Path too long after joining");
        return -1;
    }

    return 0;
}

/* Extract directory portion of a filepath; returns "." if none */
int path_get_directory(const char *filepath, char *directory, size_t directory_size) {
    ERROR_CHECK(filepath != NULL, ERR_INVALID_ARGS, "Filepath cannot be NULL");
    ERROR_CHECK(directory != NULL, ERR_INVALID_ARGS, "Directory buffer cannot be NULL");
    ERROR_CHECK(directory_size > 0, ERR_INVALID_ARGS, "Directory buffer size must be positive");

    const char *last_slash = strrchr(filepath, '/');
    if (!last_slash) {
        strncpy(directory, ".", directory_size - 1);
        directory[directory_size - 1] = '\0';
        return 0;
    }

    size_t dir_len = last_slash - filepath;
    if (dir_len >= directory_size) {
        ERROR_SET(ERR_INVALID_ARGS, "Directory path too long");
        return -1;
    }

    strncpy(directory, filepath, dir_len);
    directory[dir_len] = '\0';

    return 0;
}

/* Check whether a path is absolute (starts with '/') */
int path_is_absolute(const char *path) {
    ERROR_CHECK(path != NULL, ERR_INVALID_ARGS, "Path cannot be NULL");

    return (path[0] == '/') ? 1 : 0;
}

/*
 * path_is_safe - Validate paths for SERVER-SIDE received filenames
 *
 * IMPORTANT: This function is used to validate filenames RECEIVED from the network
 * (i.e., filenames sent by remote clients in IFTP START packets).
 *
 * Security rationale and checks:
 * - Reject any use of ".." to avoid parent-directory traversal attacks
 * - Reject absolute paths to keep created files inside server cwd
 * - Reject embedded NULL bytes
 */
int path_is_safe(const char *path) {
    // Validate pointer
    ERROR_CHECK(path != NULL, ERR_INVALID_ARGS, "Path cannot be NULL");

    /* Check for dangerous patterns in RECEIVED filenames */
    if (strstr(path, "..") != NULL) {
        return 0;  /* Contains parent directory references - SECURITY RISK */
    }

    if (path[0] == '/') {
        return 0;  /* Absolute paths not allowed for received files - SECURITY RISK */
    }

    /* Check for null bytes */
    if (strlen(path) != strcspn(path, "\0")) {
        return 0;  /* Contains null bytes - SECURITY RISK */
    }

    return 1;  /* Safe path for received filename */
}

/* Transfer statistics functions */

/* Initialize transfer statistics struct */
void file_transfer_stats_init(file_transfer_stats_t *stats) {
    if (stats) {
        memset(stats, 0, sizeof(file_transfer_stats_t));
        stats->start_time = time(NULL);
    }
}

/* Update transfer stats with bytes transferred since last call */
void file_transfer_stats_update(file_transfer_stats_t *stats, uint64_t bytes_transferred) {
    if (!stats) return;

    stats->bytes_transferred += bytes_transferred;

    if (stats->total_bytes > 0) {
        stats->transfer_rate = (double)stats->bytes_transferred /
                              (double)(time(NULL) - stats->start_time + 1);
    }
}

/* Finalize stats and compute average transfer rate */
void file_transfer_stats_finalize(file_transfer_stats_t *stats) {
    if (!stats) return;

    stats->end_time = time(NULL);
    time_t elapsed = stats->end_time - stats->start_time;
    if (elapsed > 0) {
        stats->transfer_rate = (double)stats->bytes_transferred / (double)elapsed;
    }
}

/* Print human-readable transfer statistics to stdout */
void file_transfer_stats_print(const file_transfer_stats_t *stats) {
    if (!stats) return;

    printf("\n=== File Transfer Statistics ===\n");
    printf("Total bytes:      %lu\n", stats->total_bytes);
    printf("Bytes transferred: %lu\n", stats->bytes_transferred);
    printf("Fragments sent:   %u\n", stats->fragments_sent);
    printf("Fragments recv:   %u\n", stats->fragments_received);
    printf("Transfer rate:    %.2f bytes/sec\n", stats->transfer_rate);
    printf("Progress:         %.1f%%\n", file_transfer_stats_get_progress(stats));

    time_t elapsed = stats->end_time > 0 ? stats->end_time - stats->start_time :
                     time(NULL) - stats->start_time;
    printf("Elapsed time:     %ld seconds\n", elapsed);
    printf("==============================\n");
}

/* Accessors for rate and progress */
double file_transfer_stats_get_rate(const file_transfer_stats_t *stats) {
    if (!stats) return 0.0;
    return stats->transfer_rate;
}

double file_transfer_stats_get_progress(const file_transfer_stats_t *stats) {
    if (!stats || stats->total_bytes == 0) return 0.0;

    return (double)stats->bytes_transferred / (double)stats->total_bytes * 100.0;
}