#include "pxl8_save.h"

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

#ifdef _WIN32
#include <direct.h>
#include <shlobj.h>
#define PATH_SEP '\\'
#else
#include <unistd.h>
#include <pwd.h>
#define PATH_SEP '/'
#endif

#include "pxl8_log.h"

typedef struct {
    u32 magic;
    u32 version;
    u32 size;
    u32 checksum;
} pxl8_save_header;

struct pxl8_save {
    char directory[PXL8_SAVE_MAX_PATH];
    u32 magic;
    u32 version;
};

static u32 pxl8_save_checksum(const u8* data, u32 size) {
    u32 hash = 2166136261u;
    for (u32 i = 0; i < size; i++) {
        hash ^= data[i];
        hash *= 16777619u;
    }
    return hash;
}

static void pxl8_save_get_slot_path(pxl8_save* save, u8 slot, char* path, size_t path_size) {
    if (slot == PXL8_SAVE_HOTRELOAD_SLOT) {
        snprintf(path, path_size, "%s%chotreload.sav", save->directory, PATH_SEP);
    } else {
        snprintf(path, path_size, "%s%csave%d.sav", save->directory, PATH_SEP, slot);
    }
}

static pxl8_result pxl8_save_ensure_directory(const char* path) {
    struct stat st;
    if (stat(path, &st) == 0) {
        return S_ISDIR(st.st_mode) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE;
    }

#ifdef _WIN32
    if (_mkdir(path) != 0 && errno != EEXIST) {
#else
    if (mkdir(path, 0755) != 0 && errno != EEXIST) {
#endif
        return PXL8_ERROR_SYSTEM_FAILURE;
    }

    return PXL8_OK;
}

pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
    if (!game_name) return NULL;

    pxl8_save* save = (pxl8_save*)calloc(1, sizeof(pxl8_save));
    if (!save) return NULL;

    save->magic = magic;
    save->version = version;

    char base_dir[PXL8_SAVE_MAX_PATH];

#ifdef _WIN32
    if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, base_dir))) {
        snprintf(save->directory, sizeof(save->directory),
                 "%s%cpxl8%c%s", base_dir, PATH_SEP, PATH_SEP, game_name);
    } else {
        free(save);
        return NULL;
    }
#else
    const char* home = getenv("HOME");
    if (!home) {
        struct passwd* pw = getpwuid(getuid());
        if (pw) home = pw->pw_dir;
    }
    if (!home) {
        free(save);
        return NULL;
    }

    snprintf(base_dir, sizeof(base_dir), "%s/.local/share", home);
    pxl8_save_ensure_directory(base_dir);

    snprintf(base_dir, sizeof(base_dir), "%s/.local/share/pxl8", home);
    pxl8_save_ensure_directory(base_dir);

    snprintf(save->directory, sizeof(save->directory),
             "%s/.local/share/pxl8/%s", home, game_name);
#endif

    if (pxl8_save_ensure_directory(save->directory) != PXL8_OK) {
        free(save);
        return NULL;
    }

    pxl8_info("Save system initialized: %s", save->directory);
    return save;
}

void pxl8_save_destroy(pxl8_save* save) {
    if (save) {
        free(save);
    }
}

pxl8_result pxl8_save_write(pxl8_save* save, u8 slot, const u8* data, u32 size) {
    if (!save) return PXL8_ERROR_NULL_POINTER;
    if (!data || size == 0) return PXL8_ERROR_NULL_POINTER;
    if (slot >= PXL8_SAVE_MAX_SLOTS && slot != PXL8_SAVE_HOTRELOAD_SLOT) {
        return PXL8_ERROR_INVALID_ARGUMENT;
    }

    char path[PXL8_SAVE_MAX_PATH];
    pxl8_save_get_slot_path(save, slot, path, sizeof(path));

    FILE* file = fopen(path, "wb");
    if (!file) {
        pxl8_error("Failed to open save file for writing: %s", path);
        return PXL8_ERROR_SYSTEM_FAILURE;
    }

    pxl8_save_header header = {
        .magic = save->magic,
        .version = save->version,
        .size = size,
        .checksum = pxl8_save_checksum(data, size)
    };

    bool success = true;
    if (fwrite(&header, sizeof(header), 1, file) != 1) success = false;
    if (success && fwrite(data, 1, size, file) != size) success = false;

    fclose(file);

    if (!success) {
        pxl8_error("Failed to write save data");
        return PXL8_ERROR_SYSTEM_FAILURE;
    }

    if (slot == PXL8_SAVE_HOTRELOAD_SLOT) {
        pxl8_debug("Hot reload state saved (%u bytes)", size);
    } else {
        pxl8_info("Game saved to slot %d (%u bytes)", slot, size);
    }

    return PXL8_OK;
}

pxl8_result pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_out) {
    if (!save) return PXL8_ERROR_NULL_POINTER;
    if (!data_out || !size_out) return PXL8_ERROR_NULL_POINTER;
    if (slot >= PXL8_SAVE_MAX_SLOTS && slot != PXL8_SAVE_HOTRELOAD_SLOT) {
        return PXL8_ERROR_INVALID_ARGUMENT;
    }

    *data_out = NULL;
    *size_out = 0;

    char path[PXL8_SAVE_MAX_PATH];
    pxl8_save_get_slot_path(save, slot, path, sizeof(path));

    FILE* file = fopen(path, "rb");
    if (!file) {
        return PXL8_ERROR_FILE_NOT_FOUND;
    }

    pxl8_save_header header;
    if (fread(&header, sizeof(header), 1, file) != 1) {
        fclose(file);
        return PXL8_ERROR_INVALID_FORMAT;
    }

    if (header.magic != save->magic) {
        fclose(file);
        pxl8_error("Invalid save file magic");
        return PXL8_ERROR_INVALID_FORMAT;
    }

    if (header.version > save->version) {
        fclose(file);
        pxl8_error("Save file version too new: %u", header.version);
        return PXL8_ERROR_INVALID_FORMAT;
    }

    u8* data = (u8*)malloc(header.size);
    if (!data) {
        fclose(file);
        return PXL8_ERROR_OUT_OF_MEMORY;
    }

    if (fread(data, 1, header.size, file) != header.size) {
        free(data);
        fclose(file);
        return PXL8_ERROR_SYSTEM_FAILURE;
    }

    fclose(file);

    u32 checksum = pxl8_save_checksum(data, header.size);
    if (checksum != header.checksum) {
        free(data);
        pxl8_error("Save file checksum mismatch");
        return PXL8_ERROR_INVALID_FORMAT;
    }

    *data_out = data;
    *size_out = header.size;

    if (slot == PXL8_SAVE_HOTRELOAD_SLOT) {
        pxl8_debug("Hot reload state loaded (%u bytes)", header.size);
    } else {
        pxl8_info("Game loaded from slot %d (%u bytes)", slot, header.size);
    }

    return PXL8_OK;
}

void pxl8_save_free(u8* data) {
    if (data) {
        free(data);
    }
}

bool pxl8_save_exists(pxl8_save* save, u8 slot) {
    if (!save) return false;
    if (slot >= PXL8_SAVE_MAX_SLOTS && slot != PXL8_SAVE_HOTRELOAD_SLOT) {
        return false;
    }

    char path[PXL8_SAVE_MAX_PATH];
    pxl8_save_get_slot_path(save, slot, path, sizeof(path));

    struct stat st;
    return stat(path, &st) == 0;
}

pxl8_result pxl8_save_delete(pxl8_save* save, u8 slot) {
    if (!save) return PXL8_ERROR_NULL_POINTER;
    if (slot >= PXL8_SAVE_MAX_SLOTS && slot != PXL8_SAVE_HOTRELOAD_SLOT) {
        return PXL8_ERROR_INVALID_ARGUMENT;
    }

    char path[PXL8_SAVE_MAX_PATH];
    pxl8_save_get_slot_path(save, slot, path, sizeof(path));

    if (remove(path) != 0 && errno != ENOENT) {
        return PXL8_ERROR_SYSTEM_FAILURE;
    }

    return PXL8_OK;
}

const char* pxl8_save_get_directory(pxl8_save* save) {
    return save ? save->directory : NULL;
}
