#include "native_report.h"

#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#ifdef WIN32
#include <direct.h>
#include <io.h>
#include <objbase.h>
#include <sys/stat.h>
#define FRIIDUMP_MKDIR(path) _mkdir(path)
#define FRIIDUMP_ACCESS(path) _access((path), 0)
#define FRIIDUMP_FILENO(file) _fileno(file)
#define FRIIDUMP_FSYNC(fd) _commit(fd)
#else
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define FRIIDUMP_MKDIR(path) mkdir((path), 0775)
#define FRIIDUMP_ACCESS(path) access((path), F_OK)
#define FRIIDUMP_FILENO(file) fileno(file)
#define FRIIDUMP_FSYNC(fd) fsync(fd)
#endif

static void report_copy(char *dst, size_t dst_size, const char *src) {
    if (!dst || dst_size == 0)
        return;
    if (!src)
        src = "";
    snprintf(dst, dst_size, "%s", src);
}

static void report_error(char *dst, size_t dst_size, const char *message) {
    report_copy(dst, dst_size, message ? message : "Unknown report error");
}

static bool report_valid_hex(const char *value, size_t length) {
    size_t i;
    if (!value || strlen(value) != length)
        return false;
    for (i = 0; i < length; i++) {
        if (!isxdigit((unsigned char) value[i]))
            return false;
    }
    return true;
}


static bool report_string_in_set(const char *value,
                                 const char *const *values,
                                 size_t value_count) {
    size_t i;

    if (!value)
        return false;
    for (i = 0; i < value_count; i++) {
        if (strcmp(value, values[i]) == 0)
            return true;
    }
    return false;
}

static bool report_valid_uuid(const char *value) {
    static const size_t hyphens[] = {8, 13, 18, 23};
    size_t i;
    size_t h;
    bool all_zero;

    if (!value || strlen(value) != 36)
        return false;

    h = 0;
    all_zero = true;
    for (i = 0; i < 36; i++) {
        if (h < sizeof(hyphens) / sizeof(hyphens[0]) && i == hyphens[h]) {
            if (value[i] != '-')
                return false;
            h++;
            continue;
        }
        if (!isxdigit((unsigned char) value[i]))
            return false;
        if (value[i] != '0')
            all_zero = false;
    }

    if (all_zero)
        return false;
    if (value[14] < '1' || value[14] > '5')
        return false;
    if (value[19] != '8' && value[19] != '9' &&
        value[19] != 'a' && value[19] != 'A' &&
        value[19] != 'b' && value[19] != 'B')
        return false;
    return true;
}

static bool report_valid_utc_timestamp(const char *value) {
    size_t i;

    if (!value || strlen(value) != 20)
        return false;
    for (i = 0; i < 20; i++) {
        if (i == 4 || i == 7) {
            if (value[i] != '-')
                return false;
        } else if (i == 10) {
            if (value[i] != 'T')
                return false;
        } else if (i == 13 || i == 16) {
            if (value[i] != ':')
                return false;
        } else if (i == 19) {
            if (value[i] != 'Z')
                return false;
        } else if (!isdigit((unsigned char) value[i])) {
            return false;
        }
    }
    return true;
}

static void report_utc_now(char *out, size_t out_size) {
    time_t now;
    struct tm utc_tm;

    if (!out || out_size == 0)
        return;

    now = time(NULL);
#ifdef WIN32
    if (gmtime_s(&utc_tm, &now) != 0) {
        out[0] = '\0';
        return;
    }
#else
    if (!gmtime_r(&now, &utc_tm)) {
        out[0] = '\0';
        return;
    }
#endif
    strftime(out, out_size, "%Y-%m-%dT%H:%M:%SZ", &utc_tm);
}

#ifndef WIN32
static bool report_random_bytes(unsigned char *bytes, size_t length) {
    int fd;
    size_t total;
    ssize_t got;

    fd = open("/dev/urandom", O_RDONLY);
    if (fd < 0)
        return false;

    total = 0;
    while (total < length) {
        got = read(fd, bytes + total, length - total);
        if (got <= 0) {
            close(fd);
            return false;
        }
        total += (size_t) got;
    }
    close(fd);
    return true;
}
#endif

static bool report_uuid(char out[37]) {
#ifdef WIN32
    GUID guid;
    HRESULT status;

    status = CoCreateGuid(&guid);
    if (FAILED(status))
        return false;

    snprintf(out, 37,
             "%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
             (unsigned long) guid.Data1,
             (unsigned int) guid.Data2,
             (unsigned int) guid.Data3,
             (unsigned int) guid.Data4[0],
             (unsigned int) guid.Data4[1],
             (unsigned int) guid.Data4[2],
             (unsigned int) guid.Data4[3],
             (unsigned int) guid.Data4[4],
             (unsigned int) guid.Data4[5],
             (unsigned int) guid.Data4[6],
             (unsigned int) guid.Data4[7]);
    return true;
#else
    unsigned char b[16];

    if (!report_random_bytes(b, sizeof(b)))
        return false;

    b[6] = (unsigned char) ((b[6] & 0x0fU) | 0x40U);
    b[8] = (unsigned char) ((b[8] & 0x3fU) | 0x80U);

    snprintf(out, 37,
             "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
             b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
             b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]);
    return true;
#endif
}

static bool report_directory_exists(const char *path) {
    struct stat st;
    if (!path || !path[0])
        return false;
    if (stat(path, &st) != 0)
        return false;
#ifdef WIN32
    return (st.st_mode & _S_IFDIR) != 0;
#else
    return S_ISDIR(st.st_mode);
#endif
}

static bool report_make_directory(const char *path) {
    char tmp[FRIIDUMP_REPORT_PATH_MAX];
    size_t i;

    if (!path || !path[0])
        return false;
    if (report_directory_exists(path))
        return true;

    report_copy(tmp, sizeof(tmp), path);
    for (i = 1; tmp[i]; i++) {
        if (tmp[i] != '/' && tmp[i] != '\\')
            continue;
#ifdef WIN32
        if (i == 2 && tmp[1] == ':')
            continue;
#endif
        {
            char saved = tmp[i];
            tmp[i] = '\0';
            if (tmp[0] && !report_directory_exists(tmp)) {
                if (FRIIDUMP_MKDIR(tmp) != 0 && errno != EEXIST)
                    return false;
            }
            tmp[i] = saved;
        }
    }

    if (!report_directory_exists(tmp)) {
        if (FRIIDUMP_MKDIR(tmp) != 0 && errno != EEXIST)
            return false;
    }
    return report_directory_exists(tmp);
}

static char report_path_separator(const char *directory) {
#ifdef WIN32
    (void) directory;
    return '\\';
#else
    (void) directory;
    return '/';
#endif
}

static const char *report_path_leaf(const char *path) {
    const char *slash;
    const char *backslash;
    const char *leaf;

    if (!path)
        return "";
    slash = strrchr(path, '/');
    backslash = strrchr(path, '\\');
    leaf = path;
    if (slash && slash + 1 > leaf)
        leaf = slash + 1;
    if (backslash && backslash + 1 > leaf)
        leaf = backslash + 1;
    return leaf;
}

static bool report_path_parent(const char *path,
                               char *output,
                               size_t output_size) {
    const char *leaf;
    size_t length;

    if (!path || !path[0] || !output || output_size == 0)
        return false;

    leaf = report_path_leaf(path);
    if (leaf == path)
        return false;

    /* Keep the final separator. This preserves POSIX root paths, Windows
     * drive roots, UNC paths, and relative subdirectories without needing
     * platform-specific special cases. report_join_path() accepts a trailing
     * separator and will not add another one. */
    length = (size_t) (leaf - path);
    if (length == 0 || length >= output_size)
        return false;

    memcpy(output, path, length);
    output[length] = '\0';
    return true;
}

static bool report_ends_with_case_insensitive(const char *value,
                                               const char *suffix) {
    size_t value_length;
    size_t suffix_length;
    size_t i;

    if (!value || !suffix)
        return false;
    value_length = strlen(value);
    suffix_length = strlen(suffix);
    if (suffix_length > value_length)
        return false;
    value += value_length - suffix_length;
    for (i = 0; i < suffix_length; i++) {
        if (tolower((unsigned char) value[i]) !=
            tolower((unsigned char) suffix[i]))
            return false;
    }
    return true;
}

static bool report_join_path(const char *directory,
                             const char *leaf,
                             char *output,
                             size_t output_size) {
    size_t directory_length;
    char separator;

    if (!directory || !directory[0] || !leaf || !leaf[0] ||
        !output || output_size == 0)
        return false;

    directory_length = strlen(directory);
    separator = report_path_separator(directory);
    return snprintf(output, output_size,
                    "%s%s%s",
                    directory,
                    (directory_length > 0 &&
                     directory[directory_length - 1] != '/' &&
                     directory[directory_length - 1] != '\\')
                        ? (separator == '\\' ? "\\" : "/")
                        : "",
                    leaf) < (int) output_size;
}

static void report_sanitize_leaf(const char *input, char *output, size_t output_size) {
    size_t used;
    bool previous_separator;
    unsigned char c;

    if (!output || output_size == 0)
        return;
    output[0] = '\0';
    if (!input)
        return;

    used = 0;
    previous_separator = false;
    while (*input && used + 1 < output_size && used < 96) {
        c = (unsigned char) *input++;
        if (isalnum(c) || c == '-' || c == '_') {
            output[used++] = (char) c;
            previous_separator = false;
        } else if (c == ' ' || c == '.' || c == '+' || c == '(' || c == ')' || c == '[' || c == ']') {
            if (!previous_separator && used > 0) {
                output[used++] = '_';
                previous_separator = true;
            }
        }
    }
    while (used > 0 && output[used - 1] == '_')
        used--;
    output[used] = '\0';
}

static bool report_derived_leaf(const friidump_native_report *report,
                                char *leaf,
                                size_t leaf_size,
                                char *error_text,
                                size_t error_text_size) {
    const char *basis;
    char fallback[128];
    size_t length;

    if (!report || !leaf || leaf_size == 0)
        return false;
    leaf[0] = '\0';

    if (report->explicit_path) {
        basis = report_path_leaf(report->requested_path);
        if (!basis[0]) {
            report_error(error_text, error_text_size,
                         "Native report pathname has no filename");
            return false;
        }
        if (strlen(basis) >= leaf_size) {
            report_error(error_text, error_text_size,
                         "Native report filename is too long");
            return false;
        }
        report_copy(leaf, leaf_size, basis);
        return true;
    }

    basis = report_path_leaf(report->name_basis);
    if (basis[0]) {
        if (strlen(basis) >= leaf_size) {
            report_error(error_text, error_text_size,
                         "Derived native report filename is too long");
            return false;
        }
        report_copy(leaf, leaf_size, basis);
        if (report_ends_with_case_insensitive(leaf, ".log"))
            leaf[strlen(leaf) - 4] = '\0';
        if (!leaf[0])
            report_copy(leaf, leaf_size, "friidump");
    } else {
        basis = report->have_title
                    ? report->title
                    : (report->drive_known ? report->model : "friidump");
        report_sanitize_leaf(basis, fallback, sizeof(fallback));
        report_copy(leaf, leaf_size, fallback[0] ? fallback : "friidump");
    }

    length = strlen(leaf);
    if (length + strlen(".friidump.json") >= leaf_size) {
        report_error(error_text, error_text_size,
                     "Derived native report filename is too long");
        return false;
    }
    strcat(leaf, ".friidump.json");
    return true;
}

static bool report_collision_path(const char *path,
                                  const char *run_id,
                                  char *output,
                                  size_t output_size) {
    static const char suffix[] = ".friidump.json";
    size_t path_length;
    size_t stem_length;

    if (!path || !path[0] || !run_id || !run_id[0] ||
        !output || output_size == 0)
        return false;

    path_length = strlen(path);
    stem_length = path_length;
    if (report_ends_with_case_insensitive(path, suffix))
        stem_length -= strlen(suffix);

    if (stem_length + 1 + strlen(run_id) + strlen(suffix) + 1 > output_size)
        return false;

    memcpy(output, path, stem_length);
    output[stem_length] = '\0';
    strcat(output, "-");
    strcat(output, run_id);
    strcat(output, suffix);
    return true;
}

static bool report_resolve_path(friidump_native_report *report,
                                char *error_text,
                                size_t error_text_size) {
    char leaf[FRIIDUMP_REPORT_PATH_MAX];
    char basis_directory[FRIIDUMP_REPORT_PATH_MAX];

    if (!report_derived_leaf(report, leaf, sizeof(leaf),
                             error_text, error_text_size))
        return false;

    if (report->explicit_path && !report->directory_override) {
        report_copy(report->final_path, sizeof(report->final_path),
                    report->requested_path);
        return report->final_path[0] != '\0';
    }

    if (report->directory_override) {
        if (!report_make_directory(report->report_dir)) {
            report_error(error_text, error_text_size,
                         "Could not create or access report directory");
            return false;
        }
        if (!report_join_path(report->report_dir, leaf,
                              report->final_path,
                              sizeof(report->final_path))) {
            report_error(error_text, error_text_size,
                         "Generated report path is too long");
            return false;
        }
        return true;
    }

    /* By default, keep the report beside the final FriiDump log (or the
     * output path used as its fallback naming basis). A basename-only log
     * still resolves in the current working directory. */
    if (report_path_parent(report->name_basis,
                           basis_directory,
                           sizeof(basis_directory))) {
        if (!report_join_path(basis_directory, leaf,
                              report->final_path,
                              sizeof(report->final_path))) {
            report_error(error_text, error_text_size,
                         "Generated report path is too long");
            return false;
        }
        return true;
    }

    report_copy(report->final_path, sizeof(report->final_path), leaf);
    return report->final_path[0] != '\0';
}

static size_t report_utf8_sequence_length(const unsigned char *p) {
    if (!p || p[0] == 0)
        return 0;
    if (p[0] < 0x80)
        return 1;
    if (p[0] >= 0xc2 && p[0] <= 0xdf &&
        p[1] >= 0x80 && p[1] <= 0xbf)
        return 2;
    if (p[0] == 0xe0 &&
        p[1] >= 0xa0 && p[1] <= 0xbf &&
        p[2] >= 0x80 && p[2] <= 0xbf)
        return 3;
    if (((p[0] >= 0xe1 && p[0] <= 0xec) ||
         (p[0] >= 0xee && p[0] <= 0xef)) &&
        p[1] >= 0x80 && p[1] <= 0xbf &&
        p[2] >= 0x80 && p[2] <= 0xbf)
        return 3;
    if (p[0] == 0xed &&
        p[1] >= 0x80 && p[1] <= 0x9f &&
        p[2] >= 0x80 && p[2] <= 0xbf)
        return 3;
    if (p[0] == 0xf0 &&
        p[1] >= 0x90 && p[1] <= 0xbf &&
        p[2] >= 0x80 && p[2] <= 0xbf &&
        p[3] >= 0x80 && p[3] <= 0xbf)
        return 4;
    if (p[0] >= 0xf1 && p[0] <= 0xf3 &&
        p[1] >= 0x80 && p[1] <= 0xbf &&
        p[2] >= 0x80 && p[2] <= 0xbf &&
        p[3] >= 0x80 && p[3] <= 0xbf)
        return 4;
    if (p[0] == 0xf4 &&
        p[1] >= 0x80 && p[1] <= 0x8f &&
        p[2] >= 0x80 && p[2] <= 0xbf &&
        p[3] >= 0x80 && p[3] <= 0xbf)
        return 4;
    return 0;
}

static void report_json_string(FILE *f, const char *value) {
    const unsigned char *p;
    size_t utf8_length;

    fputc('"', f);
    if (value) {
        for (p = (const unsigned char *) value; *p; p++) {
            if (*p >= 0x80) {
                utf8_length = report_utf8_sequence_length(p);
                if (utf8_length > 0) {
                    fwrite(p, 1, utf8_length, f);
                    p += utf8_length - 1;
                } else {
                    fprintf(f, "\\u%04x", (unsigned int) *p);
                }
                continue;
            }
            switch (*p) {
                case '\\': fputs("\\\\", f); break;
                case '"': fputs("\\\"", f); break;
                case '\b': fputs("\\b", f); break;
                case '\f': fputs("\\f", f); break;
                case '\n': fputs("\\n", f); break;
                case '\r': fputs("\\r", f); break;
                case '\t': fputs("\\t", f); break;
                default:
                    if (*p < 0x20)
                        fprintf(f, "\\u%04x", (unsigned int) *p);
                    else
                        fputc(*p, f);
                    break;
            }
        }
    }
    fputc('"', f);
}

static void report_json_nullable_string(FILE *f, const char *value, bool present) {
    if (present)
        report_json_string(f, value);
    else
        fputs("null", f);
}

static void report_json_nullable_u64(FILE *f, uint64_t value, bool present) {
    if (present)
        fprintf(f, "%llu", (unsigned long long) value);
    else
        fputs("null", f);
}

static void report_json_nullable_double(FILE *f, double value, bool present) {
    if (present)
        fprintf(f, "%.3f", value < 0.0 ? 0.0 : value);
    else
        fputs("null", f);
}

static bool report_write_document(FILE *f, const friidump_native_report *r) {
    size_t i;
    const friidump_native_artifact *artifact;

    fputs("{\n", f);
    fputs("  \"schema\": \"friidump-test-result.v1\",\n", f);
    fputs("  \"format\": {\n", f);
    fputs("    \"name\": \"FriiDump Interchange Format\",\n", f);
    fputs("    \"generation\": 1,\n", f);
    fputs("    \"serialization\": \"json\"\n", f);
    fputs("  },\n", f);
    fputs("  \"generated_utc\": ", f); report_json_string(f, r->generated_utc); fputs(",\n", f);
    fputs("  \"generator\": {\n", f);
    fputs("    \"name\": \"FriiDump\",\n", f);
    fputs("    \"version\": ", f); report_json_string(f, r->generator_version); fputs(",\n", f);
    fputs("    \"build_commit\": ", f); report_json_nullable_string(f, r->build_commit, r->build_commit_known); fputs("\n", f);
    fputs("  },\n", f);
    fputs("  \"run\": {\n", f);
    fputs("    \"run_id\": ", f); report_json_string(f, r->run_id); fputs(",\n", f);
    fputs("    \"started_utc\": ", f); report_json_nullable_string(f, r->started_utc, r->started_utc[0] != '\0'); fputs(",\n", f);
    fputs("    \"completed_utc\": ", f); report_json_nullable_string(f, r->completed_utc, r->completed_utc[0] != '\0'); fputs(",\n", f);
    fputs("    \"test_type\": ", f); report_json_string(f, r->test_type); fputs(",\n", f);
    fputs("    \"result\": ", f); report_json_string(f, r->run_result); fputs("\n", f);
    fputs("  },\n", f);
    fputs("  \"drive\": {\n", f);
    fputs("    \"vendor\": ", f); report_json_string(f, r->vendor); fputs(",\n", f);
    fputs("    \"model\": ", f); report_json_string(f, r->model); fputs(",\n", f);
    fputs("    \"firmware_revision\": ", f); report_json_string(f, r->firmware_revision); fputs(",\n", f);
    fputs("    \"interface\": ", f); report_json_string(f, r->interface_name); fputs(",\n", f);
    fputs("    \"device_path\": ", f); report_json_nullable_string(f, r->device_path, r->have_device_path); fputs(",\n", f);
    fprintf(f, "    \"firmware_modified\": %s,\n", r->firmware_modified ? "true" : "false");
    fputs("    \"modification_note\": ", f); report_json_nullable_string(f, r->modification_note, r->have_modification_note); fputs("\n", f);
    fputs("  },\n", f);
    fputs("  \"media\": {\n", f);
    fputs("    \"platform\": ", f); report_json_string(f, r->platform); fputs(",\n", f);
    fputs("    \"title\": ", f); report_json_nullable_string(f, r->title, r->have_title); fputs(",\n", f);
    fputs("    \"region\": ", f); report_json_nullable_string(f, r->region, r->have_region); fputs(",\n", f);
    fputs("    \"disc_id\": ", f); report_json_nullable_string(f, r->disc_id, r->have_disc_id); fputs("\n", f);
    fputs("  },\n", f);
    fputs("  \"seed\": {\n", f);
    fprintf(f, "    \"attempted\": %s,\n", r->seed_attempted ? "true" : "false");
    fputs("    \"result\": ", f); report_json_string(f, r->seed_result); fputs(",\n", f);
    fputs("    \"duration_seconds\": ", f); report_json_nullable_double(f, r->seed_duration_seconds, r->have_seed_duration); fputs("\n", f);
    fputs("  },\n", f);
    fputs("  \"dump\": {\n", f);
    fprintf(f, "    \"attempted\": %s,\n", r->dump_attempted ? "true" : "false");
    fputs("    \"result\": ", f); report_json_string(f, r->dump_result); fputs(",\n", f);
    fputs("    \"sector_count\": ", f); report_json_nullable_u64(f, r->sector_count, r->have_sector_count); fputs(",\n", f);
    fputs("    \"byte_count\": ", f); report_json_nullable_u64(f, r->byte_count, r->have_byte_count); fputs(",\n", f);
    fputs("    \"duration_seconds\": ", f); report_json_nullable_double(f, r->dump_duration_seconds, r->have_dump_duration); fputs(",\n", f);
    fputs("    \"failure_stage\": ", f); report_json_nullable_string(f, r->failure_stage, r->have_failure_stage); fputs(",\n", f);
    fputs("    \"failure_sector\": ", f); report_json_nullable_u64(f, r->failure_sector, r->have_failure_sector); fputs(",\n", f);
    fputs("    \"output_path\": ", f); report_json_nullable_string(f, r->output_path, r->have_output_path); fputs("\n", f);
    fputs("  },\n", f);
    fputs("  \"hashes\": {\n", f);
    fputs("    \"crc32\": ", f); report_json_nullable_string(f, r->crc32, report_valid_hex(r->crc32, 8)); fputs(",\n", f);
    fputs("    \"md5\": ", f); report_json_nullable_string(f, r->md5, report_valid_hex(r->md5, 32)); fputs(",\n", f);
    fputs("    \"sha1\": ", f); report_json_nullable_string(f, r->sha1, report_valid_hex(r->sha1, 40)); fputs(",\n", f);
    fputs("    \"sha256\": ", f); report_json_nullable_string(f, r->sha256, report_valid_hex(r->sha256, 64)); fputs("\n", f);
    fputs("  },\n", f);
    fputs("  \"reference\": {\n", f);
    fputs("    \"provider\": ", f); report_json_nullable_string(f, r->reference_provider, r->have_reference_provider); fputs(",\n", f);
    fputs("    \"result\": ", f); report_json_string(f, r->reference_result); fputs(",\n", f);
    fputs("    \"reference_id\": ", f); report_json_nullable_string(f, r->reference_id, r->have_reference_id); fputs("\n", f);
    fputs("  },\n", f);
    fputs("  \"profile\": {\n", f);
    fputs("    \"support_tier\": ", f); report_json_nullable_string(f, r->support_tier, r->have_support_tier); fputs(",\n", f);
    fputs("    \"cdb_offset\": ", f); report_json_nullable_string(f, r->cdb_offset, r->have_cdb_offset); fputs(",\n", f);
    fputs("    \"gate_address\": ", f); report_json_nullable_string(f, r->gate_address, r->have_gate_address); fputs("\n", f);
    fputs("  },\n", f);
    fputs("  \"notes\": [", f);
    if (r->note_count > 0)
        fputc('\n', f);
    for (i = 0; i < r->note_count; i++) {
        fputs("    ", f);
        report_json_string(f, r->notes[i]);
        fputs((i + 1 < r->note_count) ? ",\n" : "\n", f);
    }
    fputs(r->note_count > 0 ? "  ],\n" : "],\n", f);
    fputs("  \"artifacts\": [", f);
    if (r->artifact_count > 0)
        fputc('\n', f);
    for (i = 0; i < r->artifact_count; i++) {
        artifact = &r->artifacts[i];
        fputs("    {\n", f);
        fputs("      \"type\": ", f); report_json_string(f, artifact->type); fputs(",\n", f);
        fputs("      \"path\": ", f); report_json_nullable_string(f, artifact->path, artifact->have_path); fputs(",\n", f);
        fputs("      \"bytes\": ", f); report_json_nullable_u64(f, artifact->bytes, artifact->have_bytes); fputs(",\n", f);
        fputs("      \"sha256\": ", f); report_json_nullable_string(f, artifact->sha256, artifact->have_sha256); fputs("\n", f);
        fputs((i + 1 < r->artifact_count) ? "    },\n" : "    }\n", f);
    }
    fputs(r->artifact_count > 0 ? "  ]\n" : "]\n", f);
    fputs("}\n", f);

    return ferror(f) == 0;
}

void friidump_native_report_init(friidump_native_report *report, const char *generator_version) {
    if (!report)
        return;
    memset(report, 0, sizeof(*report));
    report_copy(report->generator_version, sizeof(report->generator_version), generator_version ? generator_version : "unknown");
    report_copy(report->test_type, sizeof(report->test_type), "other");
    report_copy(report->run_result, sizeof(report->run_result), "fail");
    report_copy(report->platform, sizeof(report->platform), "unknown");
    report_copy(report->seed_result, sizeof(report->seed_result), "not_attempted");
    report_copy(report->dump_result, sizeof(report->dump_result), "not_attempted");
    report_copy(report->reference_result, sizeof(report->reference_result), "not_checked");
    report_utc_now(report->started_utc, sizeof(report->started_utc));
    if (!report_uuid(report->run_id))
        report->run_id[0] = '\0';
}

bool friidump_native_report_enable_default(friidump_native_report *report) {
    if (!report)
        return false;
    report->enabled = true;
    return true;
}

bool friidump_native_report_enable_path(friidump_native_report *report, const char *path) {
    if (!report || !path || !path[0] || strlen(path) >= sizeof(report->requested_path))
        return false;
    report->enabled = true;
    report->explicit_path = true;
    report_copy(report->requested_path, sizeof(report->requested_path), path);
    return true;
}

bool friidump_native_report_enable_dir(friidump_native_report *report, const char *directory) {
    if (!report || !directory || !directory[0] || strlen(directory) >= sizeof(report->report_dir))
        return false;
    report->enabled = true;
    report->directory_override = true;
    report_copy(report->report_dir, sizeof(report->report_dir), directory);
    return true;
}

bool friidump_native_report_set_name_basis(friidump_native_report *report,
                                           const char *path) {
    if (!report || !path || !path[0] || strlen(path) >= sizeof(report->name_basis))
        return false;
    report_copy(report->name_basis, sizeof(report->name_basis), path);
    return true;
}

bool friidump_native_report_is_enabled(const friidump_native_report *report) {
    return report && report->enabled;
}

void friidump_native_report_set_build_commit(friidump_native_report *report, const char *commit) {
    if (!report)
        return;
    report->build_commit_known = report_valid_hex(commit, 40);
    report_copy(report->build_commit, sizeof(report->build_commit), report->build_commit_known ? commit : "");
}

void friidump_native_report_set_drive(friidump_native_report *report,
                                      const char *vendor,
                                      const char *model,
                                      const char *firmware_revision,
                                      const char *interface_name,
                                      const char *device_path) {
    if (!report)
        return;
    report_copy(report->vendor, sizeof(report->vendor), vendor ? vendor : "");
    report_copy(report->model, sizeof(report->model), model ? model : "");
    report_copy(report->firmware_revision, sizeof(report->firmware_revision), firmware_revision ? firmware_revision : "");
    report_copy(report->interface_name, sizeof(report->interface_name), interface_name ? interface_name : "");
    report->have_device_path = device_path && device_path[0];
    report_copy(report->device_path, sizeof(report->device_path), report->have_device_path ? device_path : "");
    report->drive_known = report->model[0] != '\0' && report->firmware_revision[0] != '\0';
}

void friidump_native_report_set_firmware_modified(friidump_native_report *report,
                                                   bool modified,
                                                   const char *note) {
    if (!report)
        return;
    report->firmware_modified = modified;
    report->have_modification_note = note && note[0] && strlen(note) <= 1000;
    report_copy(report->modification_note, sizeof(report->modification_note), report->have_modification_note ? note : "");
}

void friidump_native_report_set_media(friidump_native_report *report,
                                      const char *platform,
                                      const char *title,
                                      const char *region,
                                      const char *disc_id) {
    if (!report)
        return;
    report_copy(report->platform, sizeof(report->platform), platform && platform[0] ? platform : "unknown");
    report->have_title = title && title[0];
    report_copy(report->title, sizeof(report->title), report->have_title ? title : "");
    report->have_region = region && region[0];
    report_copy(report->region, sizeof(report->region), report->have_region ? region : "");
    report->have_disc_id = disc_id && disc_id[0];
    report_copy(report->disc_id, sizeof(report->disc_id), report->have_disc_id ? disc_id : "");
}

void friidump_native_report_set_profile(friidump_native_report *report,
                                        const char *support_tier,
                                        uint32_t cdb_offset,
                                        uint32_t gate_address) {
    if (!report)
        return;
    report->have_support_tier = support_tier && support_tier[0] && strcmp(support_tier, "none") != 0;
    report_copy(report->support_tier, sizeof(report->support_tier), report->have_support_tier ? support_tier : "");
    report->have_cdb_offset = cdb_offset != 0;
    report->have_gate_address = gate_address != 0;
    if (report->have_cdb_offset)
        snprintf(report->cdb_offset, sizeof(report->cdb_offset), "0x%x", (unsigned int) cdb_offset);
    if (report->have_gate_address)
        snprintf(report->gate_address, sizeof(report->gate_address), "0x%x", (unsigned int) gate_address);
}

void friidump_native_report_set_seed(friidump_native_report *report,
                                     bool attempted,
                                     const char *result,
                                     bool have_duration,
                                     double duration_seconds) {
    if (!report)
        return;
    report->seed_attempted = attempted;
    report_copy(report->seed_result, sizeof(report->seed_result), result ? result : "not_attempted");
    report->have_seed_duration = have_duration;
    report->seed_duration_seconds = duration_seconds;
}

void friidump_native_report_set_dump(friidump_native_report *report,
                                     bool attempted,
                                     const char *result,
                                     bool have_sector_count,
                                     uint64_t sector_count,
                                     bool have_byte_count,
                                     uint64_t byte_count,
                                     bool have_duration,
                                     double duration_seconds,
                                     const char *failure_stage,
                                     bool have_failure_sector,
                                     uint64_t failure_sector,
                                     const char *output_path) {
    if (!report)
        return;
    report->dump_attempted = attempted;
    report_copy(report->dump_result, sizeof(report->dump_result), result ? result : "not_attempted");
    report->have_sector_count = have_sector_count;
    report->sector_count = sector_count;
    report->have_byte_count = have_byte_count;
    report->byte_count = byte_count;
    report->have_dump_duration = have_duration;
    report->dump_duration_seconds = duration_seconds;
    report->have_failure_stage = failure_stage && failure_stage[0];
    report_copy(report->failure_stage, sizeof(report->failure_stage), report->have_failure_stage ? failure_stage : "");
    report->have_failure_sector = have_failure_sector;
    report->failure_sector = failure_sector;
    report->have_output_path = output_path && output_path[0];
    report_copy(report->output_path, sizeof(report->output_path), report->have_output_path ? output_path : "");
}

void friidump_native_report_set_hashes(friidump_native_report *report,
                                       const char *crc32,
                                       const char *md5,
                                       const char *sha1,
                                       const char *sha256) {
    if (!report)
        return;
    report_copy(report->crc32, sizeof(report->crc32), crc32 ? crc32 : "");
    report_copy(report->md5, sizeof(report->md5), md5 ? md5 : "");
    report_copy(report->sha1, sizeof(report->sha1), sha1 ? sha1 : "");
    report_copy(report->sha256, sizeof(report->sha256), sha256 ? sha256 : "");
}

void friidump_native_report_set_reference(friidump_native_report *report,
                                          const char *provider,
                                          const char *result,
                                          const char *reference_id) {
    if (!report)
        return;
    report->have_reference_provider = provider && provider[0];
    report_copy(report->reference_provider, sizeof(report->reference_provider), report->have_reference_provider ? provider : "");
    report_copy(report->reference_result, sizeof(report->reference_result), result ? result : "not_checked");
    report->have_reference_id = reference_id && reference_id[0];
    report_copy(report->reference_id, sizeof(report->reference_id), report->have_reference_id ? reference_id : "");
}

void friidump_native_report_set_outcome(friidump_native_report *report,
                                        const char *test_type,
                                        const char *run_result) {
    if (!report)
        return;
    report_copy(report->test_type, sizeof(report->test_type), test_type ? test_type : "other");
    report_copy(report->run_result, sizeof(report->run_result), run_result ? run_result : "fail");
}

bool friidump_native_report_add_note(friidump_native_report *report, const char *note) {
    if (!report || !note || !note[0] ||
        strlen(note) > FRIIDUMP_REPORT_TEXT_MAX ||
        report->note_count >= FRIIDUMP_REPORT_NOTE_MAX)
        return false;
    report_copy(report->notes[report->note_count], sizeof(report->notes[report->note_count]), note);
    report->note_count++;
    return true;
}

bool friidump_native_report_add_artifact(friidump_native_report *report,
                                         const char *type,
                                         const char *path,
                                         bool have_bytes,
                                         uint64_t bytes,
                                         const char *sha256) {
    friidump_native_artifact *artifact;

    if (!report || !type || !type[0] || strlen(type) > 80 ||
        report->artifact_count >= FRIIDUMP_REPORT_ARTIFACT_MAX)
        return false;
    if (path && strlen(path) > FRIIDUMP_REPORT_PATH_MAX)
        return false;
    if (sha256 && sha256[0] && !report_valid_hex(sha256, 64))
        return false;

    artifact = &report->artifacts[report->artifact_count];
    memset(artifact, 0, sizeof(*artifact));
    report_copy(artifact->type, sizeof(artifact->type), type);
    artifact->have_path = path && path[0];
    report_copy(artifact->path, sizeof(artifact->path), artifact->have_path ? path : "");
    artifact->have_bytes = have_bytes;
    artifact->bytes = bytes;
    artifact->have_sha256 = sha256 && sha256[0];
    report_copy(artifact->sha256, sizeof(artifact->sha256), artifact->have_sha256 ? sha256 : "");
    report->artifact_count++;
    return true;
}


static bool report_validate_for_write(const friidump_native_report *report,
                                      char *error_text,
                                      size_t error_text_size) {
    static const char *const platforms[] = {
        "gamecube", "wii", "xbox", "dvd", "unknown"
    };
    static const char *const test_types[] = {
        "full_dump", "seed_only", "partial_dump", "diagnostic_no_media",
        "diagnostic_wrong_media", "media_transition", "other"
    };
    static const char *const run_results[] = {
        "pass", "partial", "fail", "not_applicable"
    };
    static const char *const step_results[] = {
        "pass", "partial", "fail", "not_attempted", "not_applicable"
    };
    static const char *const reference_results[] = {
        "match", "mismatch", "not_checked", "not_applicable"
    };

    if (!report->generator_version[0]) {
        report_error(error_text, error_text_size, "Generator version is empty");
        return false;
    }
    if (!report_valid_uuid(report->run_id)) {
        report_error(error_text, error_text_size, "Run UUID is missing or invalid");
        return false;
    }
    if (!report_valid_utc_timestamp(report->started_utc) ||
        !report_valid_utc_timestamp(report->completed_utc) ||
        !report_valid_utc_timestamp(report->generated_utc)) {
        report_error(error_text, error_text_size, "Report timestamps are missing or invalid");
        return false;
    }
    if (strcmp(report->completed_utc, report->started_utc) < 0) {
        report_error(error_text, error_text_size, "Completion timestamp precedes start timestamp");
        return false;
    }
    if (!report->drive_known) {
        report_error(error_text, error_text_size,
                     "Drive identity is incomplete; no database-compatible report was written");
        return false;
    }
    if (report->firmware_modified && !report->have_modification_note) {
        report_error(error_text, error_text_size,
                     "Modified firmware requires a modification note");
        return false;
    }
    if (!report_string_in_set(report->platform, platforms,
                              sizeof(platforms) / sizeof(platforms[0])) ||
        !report_string_in_set(report->test_type, test_types,
                              sizeof(test_types) / sizeof(test_types[0])) ||
        !report_string_in_set(report->run_result, run_results,
                              sizeof(run_results) / sizeof(run_results[0])) ||
        !report_string_in_set(report->seed_result, step_results,
                              sizeof(step_results) / sizeof(step_results[0])) ||
        !report_string_in_set(report->dump_result, step_results,
                              sizeof(step_results) / sizeof(step_results[0])) ||
        !report_string_in_set(report->reference_result, reference_results,
                              sizeof(reference_results) / sizeof(reference_results[0]))) {
        report_error(error_text, error_text_size, "One or more report enum values are invalid");
        return false;
    }
    if ((report->have_seed_duration &&
         (!isfinite(report->seed_duration_seconds) || report->seed_duration_seconds < 0.0)) ||
        (report->have_dump_duration &&
         (!isfinite(report->dump_duration_seconds) || report->dump_duration_seconds < 0.0))) {
        report_error(error_text, error_text_size, "A report duration is negative or non-finite");
        return false;
    }
    if (report->note_count > FRIIDUMP_REPORT_NOTE_MAX ||
        report->artifact_count > FRIIDUMP_REPORT_ARTIFACT_MAX) {
        report_error(error_text, error_text_size,
                     "Report note or artifact count exceeds the implementation limit");
        return false;
    }
    {
        size_t i;
        for (i = 0; i < report->note_count; i++) {
            if (!report->notes[i][0] ||
                strlen(report->notes[i]) > FRIIDUMP_REPORT_TEXT_MAX) {
                report_error(error_text, error_text_size,
                             "One or more report notes are invalid");
                return false;
            }
        }
    }
    if (report->seed_attempted) {
        if (strcmp(report->seed_result, "pass") != 0 &&
            strcmp(report->seed_result, "partial") != 0 &&
            strcmp(report->seed_result, "fail") != 0) {
            report_error(error_text, error_text_size,
                         "An attempted seed stage requires pass, partial, or fail");
            return false;
        }
    } else {
        if ((strcmp(report->seed_result, "not_attempted") != 0 &&
             strcmp(report->seed_result, "not_applicable") != 0) ||
            report->have_seed_duration) {
            report_error(error_text, error_text_size,
                         "A non-attempted seed stage cannot claim a result or duration");
            return false;
        }
    }
    if (report->dump_attempted) {
        if (strcmp(report->dump_result, "pass") != 0 &&
            strcmp(report->dump_result, "partial") != 0 &&
            strcmp(report->dump_result, "fail") != 0) {
            report_error(error_text, error_text_size,
                         "An attempted dump requires pass, partial, or fail");
            return false;
        }
    } else {
        if ((strcmp(report->dump_result, "not_attempted") != 0 &&
             strcmp(report->dump_result, "not_applicable") != 0) ||
            report->have_sector_count || report->have_byte_count ||
            report->have_dump_duration || report->have_failure_stage ||
            report->have_failure_sector || report->have_output_path) {
            report_error(error_text, error_text_size,
                         "A non-attempted dump cannot claim output, progress, failure, or duration");
            return false;
        }
    }
    if (strcmp(report->dump_result, "pass") == 0 &&
        (strcmp(report->test_type, "full_dump") != 0 ||
         strcmp(report->run_result, "pass") != 0)) {
        report_error(error_text, error_text_size,
                     "A successful dump requires a full_dump/pass run outcome");
        return false;
    }
    if (strcmp(report->dump_result, "partial") == 0 &&
        (strcmp(report->test_type, "partial_dump") != 0 ||
         strcmp(report->run_result, "partial") != 0)) {
        report_error(error_text, error_text_size,
                     "A partial dump requires a partial_dump/partial run outcome");
        return false;
    }
    if (strcmp(report->dump_result, "fail") == 0 &&
        strcmp(report->run_result, "fail") != 0) {
        report_error(error_text, error_text_size,
                     "A failed dump requires a failed run outcome");
        return false;
    }
    if (strcmp(report->test_type, "seed_only") == 0 &&
        (!report->seed_attempted || report->dump_attempted)) {
        report_error(error_text, error_text_size,
                     "A seed_only run requires an attempted seed stage and no dump");
        return false;
    }
    if ((strcmp(report->test_type, "diagnostic_no_media") == 0 ||
         strcmp(report->test_type, "diagnostic_wrong_media") == 0 ||
         strcmp(report->test_type, "media_transition") == 0) &&
        (strcmp(report->run_result, "not_applicable") != 0 ||
         report->dump_attempted)) {
        report_error(error_text, error_text_size,
                     "Diagnostic and media-transition runs must be not_applicable and must not dump");
        return false;
    }
    if (strcmp(report->dump_result, "pass") == 0 &&
        (!report->have_sector_count || !report->have_byte_count)) {
        report_error(error_text, error_text_size,
                     "A successful dump requires sector and byte counts");
        return false;
    }
    if (strcmp(report->dump_result, "fail") == 0 && !report->have_failure_stage) {
        report_error(error_text, error_text_size,
                     "A failed dump requires a failure stage");
        return false;
    }
    if (strcmp(report->reference_result, "match") == 0 &&
        !report->have_reference_provider) {
        report_error(error_text, error_text_size,
                     "A reference match requires a provider");
        return false;
    }
    {
        size_t i;
        size_t dump_output_count = 0;
        const friidump_native_artifact *artifact;
        for (i = 0; i < report->artifact_count; i++) {
            artifact = &report->artifacts[i];
            if (!artifact->type[0] || strlen(artifact->type) > 80 ||
                (artifact->have_path && strlen(artifact->path) > FRIIDUMP_REPORT_PATH_MAX) ||
                (artifact->have_sha256 && !report_valid_hex(artifact->sha256, 64))) {
                report_error(error_text, error_text_size,
                             "One or more artifact records are invalid");
                return false;
            }
            if (strcmp(artifact->type, "dump_output") == 0) {
                dump_output_count++;
                if (!artifact->have_path || !report->have_output_path ||
                    strcmp(artifact->path, report->output_path) != 0 ||
                    (report->have_byte_count &&
                     (!artifact->have_bytes || artifact->bytes != report->byte_count)) ||
                    (report_valid_hex(report->sha256, 64) &&
                     (!artifact->have_sha256 ||
                      strcmp(artifact->sha256, report->sha256) != 0))) {
                    report_error(error_text, error_text_size,
                                 "The dump_output artifact does not match the reported output");
                    return false;
                }
            }
        }
        if (report->dump_attempted && report->have_output_path &&
            dump_output_count != 1) {
            report_error(error_text, error_text_size,
                         "A dump with an output path requires exactly one dump_output artifact");
            return false;
        }
    }
    return true;
}

bool friidump_native_report_write(friidump_native_report *report,
                                  char *written_path,
                                  size_t written_path_size,
                                  char *error_text,
                                  size_t error_text_size) {
    FILE *f;
    char temporary_path[FRIIDUMP_REPORT_PATH_MAX + 64];
    int close_status;

    if (written_path && written_path_size > 0)
        written_path[0] = '\0';
    if (error_text && error_text_size > 0)
        error_text[0] = '\0';

    if (!report || !report->enabled) {
        report_error(error_text, error_text_size, "Native report is not enabled");
        return false;
    }
    report_utc_now(report->completed_utc, sizeof(report->completed_utc));
    report_utc_now(report->generated_utc, sizeof(report->generated_utc));

    if (!report_validate_for_write(report, error_text, error_text_size))
        return false;
    if (!report_resolve_path(report, error_text, error_text_size))
        return false;
    if (FRIIDUMP_ACCESS(report->final_path) == 0) {
        if (report->explicit_path) {
            report_error(error_text, error_text_size,
                         "Refusing to overwrite an existing native report");
            return false;
        }
        if (!report_collision_path(report->final_path,
                                   report->run_id,
                                   report->final_path,
                                   sizeof(report->final_path))) {
            report_error(error_text, error_text_size,
                         "Could not generate a collision-safe native report path");
            return false;
        }
        if (FRIIDUMP_ACCESS(report->final_path) == 0) {
            report_error(error_text, error_text_size,
                         "Collision-safe native report path already exists");
            return false;
        }
    }

    if (snprintf(temporary_path, sizeof(temporary_path), "%s.tmp-%s",
                 report->final_path, report->run_id) >= (int) sizeof(temporary_path)) {
        report_error(error_text, error_text_size, "Temporary report path is too long");
        return false;
    }

    if (FRIIDUMP_ACCESS(temporary_path) == 0 && remove(temporary_path) != 0) {
        report_error(error_text, error_text_size, "Could not remove a stale temporary report");
        return false;
    }

    f = fopen(temporary_path, "wb");
    if (!f) {
        report_error(error_text, error_text_size, "Could not open temporary native report");
        return false;
    }

    if (!report_write_document(f, report) || fflush(f) != 0 || FRIIDUMP_FSYNC(FRIIDUMP_FILENO(f)) != 0) {
        fclose(f);
        remove(temporary_path);
        report_error(error_text, error_text_size, "Could not write or flush native report");
        return false;
    }

    close_status = fclose(f);
    if (close_status != 0) {
        remove(temporary_path);
        report_error(error_text, error_text_size, "Could not close temporary native report");
        return false;
    }

#ifdef WIN32
    if (!MoveFileExA(temporary_path, report->final_path, MOVEFILE_WRITE_THROUGH)) {
        remove(temporary_path);
        report_error(error_text, error_text_size, "Could not atomically publish native report");
        return false;
    }
#else
    if (link(temporary_path, report->final_path) != 0) {
        remove(temporary_path);
        if (errno == EEXIST)
            report_error(error_text, error_text_size, "Refusing to overwrite an existing native report");
        else
            report_error(error_text, error_text_size, "Could not atomically publish native report");
        return false;
    }
    if (unlink(temporary_path) != 0) {
        remove(report->final_path);
        report_error(error_text, error_text_size, "Could not finalize the atomic native report publication");
        return false;
    }
#endif

    report_copy(written_path, written_path_size, report->final_path);
    return true;
}
