// saveHost.c - Host-cc harness for the save-file core (src/core/save.c).
//
// Built by scripts/check-save.sh on the serialHost.c pattern: compiled with
// -DJOEYLIB_PLATFORM_BLANK plus the JL_HAS_SAVE_* flags so port.h routes
// jlpSave*/jlpDiskFree to the POSIX HAL below. Unlike the serial mock, this
// exercises the REAL core end to end -- jlSaveWrite/Read/Open/Exists/Delete do
// actual stdio into a SAVES/ directory in the current working directory, which
// check-save.sh points at a throwaway temp dir. Prints one line per check and
// a final tally; exits nonzero on any failure, so it doubles as a CI gate.

#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/statvfs.h>

#include "joey/file.h"
#include "port.h"


// ----- POSIX save HAL (satisfies the jlpSave* dispatch under -DJL_HAS_SAVE_*) -----

uint32_t jlpDiskFree(void) {
    struct statvfs      vfs;
    unsigned long long  bytes;

    if (statvfs(".", &vfs) != 0) {
        return 0u;
    }
    bytes = (unsigned long long)vfs.f_bavail * (unsigned long long)vfs.f_frsize;
    if (bytes > 0xFFFFFFFFull) {
        return 0xFFFFFFFFu;
    }
    return (uint32_t)bytes;
}


bool jlpSaveDelete(const char *path) {
    return remove(path) == 0;
}


bool jlpSaveDirEnsure(const char *dir) {
    struct stat  st;

    if (mkdir(dir, 0777) == 0) {
        return true;
    }
    return stat(dir, &st) == 0 && S_ISDIR(st.st_mode);
}


// ----- Test harness -----

static int gPass = 0;
static int gFail = 0;


static void check(const char *name, bool ok) {
    if (ok) {
        gPass++;
        printf("  ok   %s\n", name);
    } else {
        gFail++;
        printf("  FAIL %s\n", name);
    }
}


int main(void) {
    uint8_t   out[64];
    uint8_t   in[64];
    uint32_t  n;
    FILE     *fp;
    int       i;

    printf("save-host: core save/read/exists/delete/diskfree (real files)\n");

    for (i = 0; i < 64; i++) {
        out[i] = (uint8_t)(i * 7 + 1);
    }

    // 1. Clean slate + a plausible free-space number.
    check("fresh: hero.sav absent", jlSaveExists("hero.sav") == false);
    check("diskFree > 0", jlDiskFree() > 0u);

    // 2. Blob write creates SAVES/ on demand and round-trips.
    check("write blob (64)", jlSaveWrite("hero.sav", out, 64u) == true);
    check("exists after write", jlSaveExists("hero.sav") == true);
    memset(in, 0, sizeof(in));
    n = jlSaveRead("hero.sav", in, sizeof(in));
    check("read full length", n == 64u);
    check("payload round-trips", memcmp(in, out, 64) == 0);

    // 3. Read honors the caller's max.
    memset(in, 0, sizeof(in));
    n = jlSaveRead("hero.sav", in, 16u);
    check("read capped at max", n == 16u);
    check("capped payload matches", memcmp(in, out, 16) == 0);

    // 4. Streaming open for incremental writes.
    fp = jlSaveOpen("stream.sav", "wb");
    check("stream open (wb)", fp != NULL);
    if (fp != NULL) {
        fputc('J', fp);
        fputc('L', fp);
        fclose(fp);
    }
    memset(in, 0, sizeof(in));
    n = jlSaveRead("stream.sav", in, sizeof(in));
    check("stream read length 2", n == 2u);
    check("stream payload 'JL'", in[0] == (uint8_t)'J' && in[1] == (uint8_t)'L');

    // 5. Zero-length write makes an empty file.
    check("write empty", jlSaveWrite("empty.sav", NULL, 0u) == true);
    check("empty exists", jlSaveExists("empty.sav") == true);
    check("empty reads 0", jlSaveRead("empty.sav", in, sizeof(in)) == 0u);

    // 6. Delete removes it; deleting a missing file reports false.
    check("delete hero.sav", jlSaveDelete("hero.sav") == true);
    check("hero.sav gone", jlSaveExists("hero.sav") == false);
    check("delete missing -> false", jlSaveDelete("nope.sav") == false);

    // 7. NULL / bad-argument guards.
    check("write NULL name", jlSaveWrite(NULL, out, 4u) == false);
    check("read NULL name", jlSaveRead(NULL, in, 4u) == 0u);
    check("read NULL buf", jlSaveRead("empty.sav", NULL, 4u) == 0u);
    check("open NULL name", jlSaveOpen(NULL, "rb") == NULL);
    check("exists NULL name", jlSaveExists(NULL) == false);
    check("delete NULL name", jlSaveDelete(NULL) == false);

    // Best-effort cleanup of the files this run created.
    jlSaveDelete("stream.sav");
    jlSaveDelete("empty.sav");

    printf("save-host: %d passed, %d failed\n", gPass, gFail);
    return (gFail == 0) ? 0 : 1;
}
