#define WIN32_LEAN_AND_MEAN
#define _CRT_SECURE_NO_WARNINGS

#include <windows.h>
#include <winioctl.h>
#include <conio.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <math.h>
#include <time.h>

/*
 * CasinoLove Drive Sustained Write Test
 * -------------------------------------
 * Native Windows 10/11 console utility written in C.
 * No third-party libraries are required.
 *
 * PURPOSE
 *   Measure sustained sequential write performance of removable or fixed
 *   storage (SSD, HDD, SD card, USB storage, etc.) in a workload intended to
 *   resemble continuous high-bitrate media recording.
 *
 * IMPORTANT
 *   This program writes a large destructive TEST FILE to the selected target
 *   directory. It does NOT format the drive and does NOT overwrite existing
 *   unrelated files, but it can consume most free space on the volume.
 *
 * BUILD (MinGW-w64 GCC)
 *   gcc -O2 -std=c11 -Wall -Wextra drive-test.c -o drive-test.exe
 *
 * The implementation uses Win32 file APIs so it can request unbuffered,
 * write-through I/O. This avoids measuring only the Windows filesystem cache.
 */

#define APP_NAME            "CasinoLove Drive Sustained Write Test"
#define APP_VERSION         "0.1.0"
#define DEFAULT_BLOCK_SIZE  (8ULL * 1024ULL * 1024ULL)
#define SAFETY_RESERVE      (256ULL * 1024ULL * 1024ULL)
#define SAMPLE_TARGET_SEC   1.0
#define GRAPH_WIDTH         60
#define GRAPH_HEIGHT        10
#define MAX_PATH_INPUT      32768
#define PATTERN_SEED        0xC45A10B9D31E7A5FULL

#ifndef FILE_FLAG_NO_BUFFERING
#define FILE_FLAG_NO_BUFFERING 0x20000000
#endif

typedef struct SpeedSamples {
    double *values;
    size_t count;
    size_t capacity;
    double min;
    double max;
    double sum;
} SpeedSamples;

typedef struct TestConfig {
    wchar_t target_dir[MAX_PATH_INPUT];
    wchar_t target_file[MAX_PATH_INPUT];
    wchar_t target_path[MAX_PATH_INPUT];
    wchar_t log_path[MAX_PATH_INPUT];
    wchar_t volume_root[MAX_PATH_INPUT];
    uint64_t target_bytes;
    double duration_limit_sec;
    double min_required_mbps;
    DWORD logical_sector;
    DWORD physical_sector;
    DWORD alignment;
    DWORD block_size;
    wchar_t filesystem[64];
    BOOL verify_after_write;
    BOOL delete_after_test;
} TestConfig;

typedef struct TestState {
    uint64_t bytes_written;
    uint64_t bytes_verified;
    uint64_t write_errors;
    uint64_t verify_errors;
    uint64_t mismatch_blocks;
    double wall_elapsed_sec;
    double write_active_sec;
    double verify_elapsed_sec;
    BOOL user_stopped;
    BOOL write_failed;
    BOOL verify_failed;
    DWORD last_error;
    SpeedSamples write_samples;
    SpeedSamples verify_samples;
} TestState;

static volatile LONG g_stop_requested = 0;
static HANDLE g_console = NULL;
static CONSOLE_CURSOR_INFO g_original_cursor;
static BOOL g_cursor_saved = FALSE;

static BOOL WINAPI console_ctrl_handler(DWORD ctrl_type)
{
    if (ctrl_type == CTRL_C_EVENT || ctrl_type == CTRL_BREAK_EVENT ||
        ctrl_type == CTRL_CLOSE_EVENT || ctrl_type == CTRL_SHUTDOWN_EVENT) {
        InterlockedExchange(&g_stop_requested, 1);
        return TRUE;
    }
    return FALSE;
}

static double qpc_seconds(void)
{
    static LARGE_INTEGER freq = {0};
    LARGE_INTEGER now;
    if (freq.QuadPart == 0) {
        QueryPerformanceFrequency(&freq);
    }
    QueryPerformanceCounter(&now);
    return (double)now.QuadPart / (double)freq.QuadPart;
}


static BOOL wide_to_utf8(const wchar_t *src, char *dst, size_t cap)
{
    if (!src || !dst || cap == 0) return FALSE;
    int needed = WideCharToMultiByte(CP_UTF8, 0, src, -1, NULL, 0, NULL, NULL);
    if (needed <= 0 || (size_t)needed > cap) return FALSE;
    return WideCharToMultiByte(CP_UTF8, 0, src, -1, dst, (int)cap, NULL, NULL) > 0;
}

static BOOL valid_simple_filename(const wchar_t *name)
{
    if (!name || name[0] == L'\0') return FALSE;
    if (wcscmp(name, L".") == 0 || wcscmp(name, L"..") == 0) return FALSE;
    const wchar_t *bad = L"\\/:*?\"<>|";
    for (const wchar_t *p = name; *p; ++p) {
        if (*p < 32 || wcschr(bad, *p)) return FALSE;
    }
    return TRUE;
}

static void trim_newline_w(wchar_t *s)
{
    size_t n = wcslen(s);
    while (n > 0 && (s[n - 1] == L'\n' || s[n - 1] == L'\r')) {
        s[--n] = L'\0';
    }
}

static BOOL read_line_w(const wchar_t *prompt, wchar_t *out, size_t cap)
{
    if (prompt) {
        wprintf(L"%ls", prompt);
        fflush(stdout);
    }
    if (!fgetws(out, (int)cap, stdin)) {
        return FALSE;
    }
    trim_newline_w(out);
    return TRUE;
}

static double prompt_double(const wchar_t *prompt, double def, double minv)
{
    wchar_t line[128];
    for (;;) {
        if (!read_line_w(prompt, line, _countof(line))) {
            return def;
        }
        if (line[0] == L'\0') {
            return def;
        }
        wchar_t *end = NULL;
        double v = wcstod(line, &end);
        if (end != line && *end == L'\0' && v >= minv) {
            return v;
        }
        wprintf(L"Invalid value. Please enter a number >= %.2f.\n", minv);
    }
}

static BOOL prompt_yes_no(const wchar_t *prompt, BOOL def)
{
    wchar_t line[32];
    for (;;) {
        if (!read_line_w(prompt, line, _countof(line))) {
            return def;
        }
        if (line[0] == L'\0') return def;
        if (_wcsicmp(line, L"y") == 0 || _wcsicmp(line, L"yes") == 0) return TRUE;
        if (_wcsicmp(line, L"n") == 0 || _wcsicmp(line, L"no") == 0) return FALSE;
        wprintf(L"Please enter Y or N.\n");
    }
}

static void format_bytes(uint64_t bytes, char *out, size_t cap)
{
    static const char *units[] = {"B", "KiB", "MiB", "GiB", "TiB"};
    double v = (double)bytes;
    int u = 0;
    while (v >= 1024.0 && u < 4) {
        v /= 1024.0;
        ++u;
    }
    if (u == 0) snprintf(out, cap, "%llu %s", (unsigned long long)bytes, units[u]);
    else snprintf(out, cap, "%.2f %s", v, units[u]);
}

static void format_duration(double sec, char *out, size_t cap)
{
    if (sec < 0 || !isfinite(sec)) {
        snprintf(out, cap, "--:--:--");
        return;
    }
    uint64_t s = (uint64_t)sec;
    uint64_t h = s / 3600;
    uint64_t m = (s % 3600) / 60;
    uint64_t r = s % 60;
    snprintf(out, cap, "%02llu:%02llu:%02llu",
             (unsigned long long)h,
             (unsigned long long)m,
             (unsigned long long)r);
}

static void print_win_error(const wchar_t *prefix, DWORD code)
{
    wchar_t *msg = NULL;
    FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
                   FORMAT_MESSAGE_IGNORE_INSERTS,
                   NULL, code, 0, (LPWSTR)&msg, 0, NULL);
    if (msg) {
        trim_newline_w(msg);
        fwprintf(stderr, L"%ls: [%lu] %ls\n", prefix, (unsigned long)code, msg);
        LocalFree(msg);
    } else {
        fwprintf(stderr, L"%ls: Windows error %lu\n", prefix, (unsigned long)code);
    }
}

static BOOL samples_add(SpeedSamples *s, double v)
{
    if (s->count == s->capacity) {
        size_t newcap = s->capacity ? s->capacity * 2 : 1024;
        double *p = (double *)realloc(s->values, newcap * sizeof(double));
        if (!p) return FALSE;
        s->values = p;
        s->capacity = newcap;
    }
    s->values[s->count++] = v;
    s->sum += v;
    if (s->count == 1 || v < s->min) s->min = v;
    if (s->count == 1 || v > s->max) s->max = v;
    return TRUE;
}

static int compare_double(const void *a, const void *b)
{
    double x = *(const double *)a;
    double y = *(const double *)b;
    return (x > y) - (x < y);
}

static double samples_median(const SpeedSamples *s)
{
    if (s->count == 0) return 0.0;
    double *copy = (double *)malloc(s->count * sizeof(double));
    if (!copy) return 0.0;
    memcpy(copy, s->values, s->count * sizeof(double));
    qsort(copy, s->count, sizeof(double), compare_double);
    double med;
    if ((s->count & 1U) != 0) med = copy[s->count / 2];
    else med = (copy[s->count / 2 - 1] + copy[s->count / 2]) / 2.0;
    free(copy);
    return med;
}

static double samples_average(const SpeedSamples *s)
{
    return s->count ? s->sum / (double)s->count : 0.0;
}

static void samples_free(SpeedSamples *s)
{
    free(s->values);
    memset(s, 0, sizeof(*s));
}

static void normalize_dir_path(wchar_t *path, size_t cap)
{
    size_t n = wcslen(path);
    if (n > 0 && path[n - 1] != L'\\' && path[n - 1] != L'/') {
        if (n + 1 < cap) {
            path[n] = L'\\';
            path[n + 1] = L'\0';
        }
    }
}

static BOOL combine_path(const wchar_t *dir, const wchar_t *file, wchar_t *out, size_t cap)
{
    int n = _snwprintf(out, cap, L"%ls%ls", dir, file);
    return n >= 0 && (size_t)n < cap;
}

static BOOL get_volume_info_for_path(const wchar_t *path, wchar_t *root, size_t root_cap,
                                     uint64_t *free_bytes, DWORD *bytes_per_sector)
{
    if (!GetVolumePathNameW(path, root, (DWORD)root_cap)) {
        return FALSE;
    }

    ULARGE_INTEGER free_avail, total, free_total;
    if (!GetDiskFreeSpaceExW(root, &free_avail, &total, &free_total)) {
        return FALSE;
    }
    *free_bytes = (uint64_t)free_avail.QuadPart;

    DWORD sectors_per_cluster = 0, bps = 0, free_clusters = 0, total_clusters = 0;
    if (!GetDiskFreeSpaceW(root, &sectors_per_cluster, &bps, &free_clusters, &total_clusters)) {
        return FALSE;
    }
    (void)sectors_per_cluster;
    (void)free_clusters;
    (void)total_clusters;
    *bytes_per_sector = bps;
    return TRUE;
}


static DWORD query_physical_sector_size(const wchar_t *volume_root)
{
    wchar_t volume_name[MAX_PATH_INPUT];
    if (!GetVolumeNameForVolumeMountPointW(volume_root, volume_name, _countof(volume_name))) {
        return 0;
    }

    size_t n = wcslen(volume_name);
    while (n > 0 && (volume_name[n - 1] == L'\\' || volume_name[n - 1] == L'/')) {
        volume_name[--n] = L'\0';
    }

    HANDLE h = CreateFileW(volume_name,
                           0,
                           FILE_SHARE_READ | FILE_SHARE_WRITE,
                           NULL,
                           OPEN_EXISTING,
                           0,
                           NULL);
    if (h == INVALID_HANDLE_VALUE) return 0;

    STORAGE_PROPERTY_QUERY query;
    memset(&query, 0, sizeof(query));
    query.PropertyId = StorageAccessAlignmentProperty;
    query.QueryType = PropertyStandardQuery;

    STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR desc;
    memset(&desc, 0, sizeof(desc));
    DWORD returned = 0;
    BOOL ok = DeviceIoControl(h,
                              IOCTL_STORAGE_QUERY_PROPERTY,
                              &query, sizeof(query),
                              &desc, sizeof(desc),
                              &returned,
                              NULL);
    CloseHandle(h);
    if (!ok || returned < sizeof(desc)) return 0;
    return desc.BytesPerPhysicalSector;
}

static void query_filesystem_name(const wchar_t *volume_root, wchar_t *out, size_t cap)
{
    if (!out || cap == 0) return;
    out[0] = L'\0';
    wchar_t fs[64];
    if (GetVolumeInformationW(volume_root, NULL, 0, NULL, NULL, NULL,
                              fs, (DWORD)_countof(fs))) {
        wcsncpy(out, fs, cap - 1);
        out[cap - 1] = L'\0';
    } else {
        wcsncpy(out, L"unknown", cap - 1);
        out[cap - 1] = L'\0';
    }
}

static BOOL same_volume(const wchar_t *path_a, const wchar_t *path_b)
{
    wchar_t ra[MAX_PATH_INPUT], rb[MAX_PATH_INPUT];
    if (!GetVolumePathNameW(path_a, ra, _countof(ra))) return FALSE;
    if (!GetVolumePathNameW(path_b, rb, _countof(rb))) return FALSE;
    return _wcsicmp(ra, rb) == 0;
}

static uint64_t align_down_u64(uint64_t v, uint64_t a)
{
    return a ? (v / a) * a : v;
}

static DWORD choose_alignment(DWORD logical_sector, DWORD physical_sector)
{
    DWORD a = logical_sector;
    if (physical_sector > a) a = physical_sector;
    if (a < 4096) a = 4096;
    /* Round to a power of two for conservative buffer/offset alignment. */
    DWORD p = 1;
    while (p < a && p < (1U << 30)) p <<= 1;
    return p;
}

static uint64_t mix64(uint64_t x)
{
    x ^= x >> 30;
    x *= 0xbf58476d1ce4e5b9ULL;
    x ^= x >> 27;
    x *= 0x94d049bb133111ebULL;
    x ^= x >> 31;
    return x;
}

/*
 * Fill an aligned block with deterministic pseudo-random data.
 * Every 64-bit word depends on its absolute file position, so a complete
 * block written to the wrong offset is detected during verification.
 *
 * Pattern generation is done before timing the corresponding WriteFile call,
 * so CPU pattern generation is not counted as device write time.
 */
static void fill_pattern(void *buffer, DWORD bytes, uint64_t file_offset)
{
    uint64_t *p = (uint64_t *)buffer;
    size_t words = bytes / sizeof(uint64_t);
    uint64_t base_word = file_offset / sizeof(uint64_t);
    for (size_t i = 0; i < words; ++i) {
        p[i] = mix64(PATTERN_SEED ^ (base_word + (uint64_t)i));
    }
    size_t rem = bytes % sizeof(uint64_t);
    if (rem) {
        uint64_t tail = mix64(PATTERN_SEED ^ (base_word + (uint64_t)words));
        memcpy((unsigned char *)buffer + words * sizeof(uint64_t), &tail, rem);
    }
}

static BOOL verify_pattern(const void *buffer, DWORD bytes, uint64_t file_offset,
                           uint64_t *first_bad_offset)
{
    const uint64_t *p = (const uint64_t *)buffer;
    size_t words = bytes / sizeof(uint64_t);
    uint64_t base_word = file_offset / sizeof(uint64_t);
    for (size_t i = 0; i < words; ++i) {
        uint64_t expected = mix64(PATTERN_SEED ^ (base_word + (uint64_t)i));
        if (p[i] != expected) {
            *first_bad_offset = file_offset + (uint64_t)i * sizeof(uint64_t);
            return FALSE;
        }
    }
    size_t rem = bytes % sizeof(uint64_t);
    if (rem) {
        uint64_t expected = mix64(PATTERN_SEED ^ (base_word + (uint64_t)words));
        if (memcmp((const unsigned char *)buffer + words * sizeof(uint64_t), &expected, rem) != 0) {
            *first_bad_offset = file_offset + (uint64_t)words * sizeof(uint64_t);
            return FALSE;
        }
    }
    return TRUE;
}

static void console_cursor_visible(BOOL visible)
{
    if (!g_console) return;
    CONSOLE_CURSOR_INFO ci;
    if (GetConsoleCursorInfo(g_console, &ci)) {
        if (!g_cursor_saved) {
            g_original_cursor = ci;
            g_cursor_saved = TRUE;
        }
        ci.bVisible = visible;
        SetConsoleCursorInfo(g_console, &ci);
    }
}

static void console_home(void)
{
    COORD pos = {0, 0};
    SetConsoleCursorPosition(g_console, pos);
}

static void console_clear(void)
{
    CONSOLE_SCREEN_BUFFER_INFO info;
    DWORD written;
    if (!GetConsoleScreenBufferInfo(g_console, &info)) return;
    DWORD cells = (DWORD)info.dwSize.X * (DWORD)info.dwSize.Y;
    COORD home = {0, 0};
    FillConsoleOutputCharacterA(g_console, ' ', cells, home, &written);
    FillConsoleOutputAttribute(g_console, info.wAttributes, cells, home, &written);
    SetConsoleCursorPosition(g_console, home);
}

static double recent_graph_max(const SpeedSamples *s, double threshold)
{
    size_t start = s->count > GRAPH_WIDTH ? s->count - GRAPH_WIDTH : 0;
    double m = threshold > 0.0 ? threshold * 1.20 : 1.0;
    for (size_t i = start; i < s->count; ++i) {
        if (s->values[i] > m) m = s->values[i];
    }
    if (m < 1.0) m = 1.0;
    return m;
}

static void draw_graph(const SpeedSamples *s, double threshold)
{
    double ymax = recent_graph_max(s, threshold);
    size_t start = s->count > GRAPH_WIDTH ? s->count - GRAPH_WIDTH : 0;
    size_t n = s->count - start;

    printf("\n  Recent sustained write speed (last %d samples, scale 0..%.1f MB/s)\n",
           GRAPH_WIDTH, ymax);

    for (int row = GRAPH_HEIGHT; row >= 1; --row) {
        double row_level = ymax * (double)row / (double)GRAPH_HEIGHT;
        printf("  %7.1f |", row_level);
        for (size_t col = 0; col < GRAPH_WIDTH; ++col) {
            if (col < GRAPH_WIDTH - n) {
                putchar(' ');
                continue;
            }
            size_t idx = start + (col - (GRAPH_WIDTH - n));
            double v = s->values[idx];
            double bottom = ymax * (double)(row - 1) / (double)GRAPH_HEIGHT;
            if (v >= row_level) putchar('#');
            else if (v > bottom) putchar('+');
            else if (threshold > 0.0 && threshold >= bottom && threshold < row_level) putchar('-');
            else putchar(' ');
        }
        printf("|\n");
    }
    printf("           +------------------------------------------------------------+\n");
    if (threshold > 0.0) printf("             '-' marks the configured %.1f MB/s threshold\n", threshold);
}

static void draw_write_ui(const TestConfig *cfg, const TestState *st, double current_mbps,
                          uint64_t sample_bytes, double sample_sec)
{
    (void)sample_bytes;
    (void)sample_sec;
    char done[64], total[64], elapsed[32], eta[32];
    format_bytes(st->bytes_written, done, sizeof(done));
    format_bytes(cfg->target_bytes, total, sizeof(total));
    format_duration(st->wall_elapsed_sec, elapsed, sizeof(elapsed));

    double overall_mbps = st->write_active_sec > 0.0
        ? ((double)st->bytes_written / 1000000.0) / st->write_active_sec : 0.0;
    double remaining = (double)(cfg->target_bytes - st->bytes_written);
    double eta_sec = overall_mbps > 0.0 ? remaining / (overall_mbps * 1000000.0) : -1.0;
    format_duration(eta_sec, eta, sizeof(eta));

    double pct = cfg->target_bytes ? 100.0 * (double)st->bytes_written / (double)cfg->target_bytes : 0.0;
    double med = samples_median(&st->write_samples);
    double avg = samples_average(&st->write_samples);
    size_t below = 0;
    if (cfg->min_required_mbps > 0.0) {
        for (size_t i = 0; i < st->write_samples.count; ++i)
            if (st->write_samples.values[i] < cfg->min_required_mbps) ++below;
    }

    console_home();
    printf("================================================================================\n");
    printf(" %s v%s\n", APP_NAME, APP_VERSION);
    printf("================================================================================\n");
    printf(" Phase              : WRITE TEST                                            \n");
    printf(" Progress           : %6.2f %%   %s / %s\n", pct, done, total);
    printf(" Elapsed            : %s   ETA: %s\n", elapsed, eta);
    printf(" Current sample     : %9.2f MB/s\n", current_mbps);
    printf(" Sample statistics  : min %8.2f | avg %8.2f | median %8.2f | max %8.2f MB/s\n",
           st->write_samples.count ? st->write_samples.min : 0.0,
           avg, med, st->write_samples.count ? st->write_samples.max : 0.0);
    printf(" Active-write avg   : %9.2f MB/s\n", overall_mbps);
    if (cfg->min_required_mbps > 0.0) {
        printf(" Required minimum   : %9.2f MB/s   below threshold samples: %llu / %llu\n",
               cfg->min_required_mbps,
               (unsigned long long)below,
               (unsigned long long)st->write_samples.count);
        printf(" Threshold status   : %-52s\n",
               below == 0 && st->write_samples.count > 0 ? "PASS SO FAR" : "FAILED AT LEAST ONCE");
    } else {
        printf(" Required minimum   : not configured                                         \n");
        printf(" Threshold status   : n/a                                                    \n");
    }
    printf(" Write errors       : %llu\n", (unsigned long long)st->write_errors);
    printf(" Stop               : Press Q or Ctrl+C                                      \n");
    draw_graph(&st->write_samples, cfg->min_required_mbps);
    printf("\n Note: MB/s uses decimal megabytes (1 MB = 1,000,000 bytes).                  \n");
    fflush(stdout);
}

static void draw_verify_ui(const TestConfig *cfg, const TestState *st, double current_mbps)
{
    char done[64], total[64], elapsed[32];
    format_bytes(st->bytes_verified, done, sizeof(done));
    format_bytes(st->bytes_written, total, sizeof(total));
    format_duration(st->verify_elapsed_sec, elapsed, sizeof(elapsed));
    double pct = st->bytes_written ? 100.0 * (double)st->bytes_verified / (double)st->bytes_written : 0.0;

    console_home();
    printf("================================================================================\n");
    printf(" %s v%s\n", APP_NAME, APP_VERSION);
    printf("================================================================================\n");
    printf(" Phase              : READ-BACK VERIFICATION                                 \n");
    printf(" Progress           : %6.2f %%   %s / %s\n", pct, done, total);
    printf(" Elapsed            : %s\n", elapsed);
    printf(" Current read speed : %9.2f MB/s\n", current_mbps);
    printf(" Read speed stats   : min %8.2f | avg %8.2f | median %8.2f | max %8.2f MB/s\n",
           st->verify_samples.count ? st->verify_samples.min : 0.0,
           samples_average(&st->verify_samples), samples_median(&st->verify_samples),
           st->verify_samples.count ? st->verify_samples.max : 0.0);
    printf(" Data mismatches    : %llu\n", (unsigned long long)st->mismatch_blocks);
    printf(" Read errors        : %llu\n", (unsigned long long)st->verify_errors);
    printf(" Stop               : Press Q or Ctrl+C                                      \n");
    printf("                                                                                \n");
    printf(" Verification compares every byte with a deterministic position-dependent     \n");
    printf(" pattern. A mismatch indicates corruption, misplaced data, or incomplete data. \n");
    (void)cfg;
    fflush(stdout);
}

static BOOL check_stop_key(void)
{
    if (InterlockedCompareExchange(&g_stop_requested, 0, 0) != 0) return TRUE;
    if (_kbhit()) {
        int c = _getch();
        if (c == 'q' || c == 'Q' || c == 3) {
            InterlockedExchange(&g_stop_requested, 1);
            return TRUE;
        }
    }
    return FALSE;
}

static void csv_header(FILE *log, const TestConfig *cfg)
{
    if (!log) return;
    time_t now = time(NULL);
    struct tm lt;
    localtime_s(&lt, &now);
    char target_utf8[MAX_PATH_INPUT * 3];
    char volume_utf8[MAX_PATH_INPUT * 3];
    char filesystem_utf8[256];
    if (!wide_to_utf8(cfg->target_path, target_utf8, sizeof(target_utf8)))
        strcpy(target_utf8, "<path conversion failed>");
    if (!wide_to_utf8(cfg->volume_root, volume_utf8, sizeof(volume_utf8)))
        strcpy(volume_utf8, "<path conversion failed>");
    if (!wide_to_utf8(cfg->filesystem, filesystem_utf8, sizeof(filesystem_utf8)))
        strcpy(filesystem_utf8, "unknown");

    fprintf(log, "# %s v%s\n", APP_NAME, APP_VERSION);
    fprintf(log, "# encoding=UTF-8\n");
    fprintf(log, "# started_local=%04d-%02d-%02dT%02d:%02d:%02d\n",
            lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday,
            lt.tm_hour, lt.tm_min, lt.tm_sec);
    fprintf(log, "# target_path=%s\n", target_utf8);
    fprintf(log, "# volume_root=%s\n", volume_utf8);
    fprintf(log, "# filesystem=%s\n", filesystem_utf8);
    fprintf(log, "# logical_sector_bytes=%lu\n", (unsigned long)cfg->logical_sector);
    fprintf(log, "# physical_sector_bytes=%lu\n", (unsigned long)cfg->physical_sector);
    fprintf(log, "# target_bytes=%llu\n", (unsigned long long)cfg->target_bytes);
    fprintf(log, "# minimum_required_mbps=%.3f\n", cfg->min_required_mbps);
    fprintf(log, "# block_size_bytes=%lu\n", (unsigned long)cfg->block_size);
    fprintf(log, "# alignment_bytes=%lu\n", (unsigned long)cfg->alignment);
    fprintf(log, "# io_mode=FILE_FLAG_NO_BUFFERING|FILE_FLAG_WRITE_THROUGH\n");
    fprintf(log, "phase,sample,elapsed_s,total_bytes,sample_bytes,sample_duration_s,speed_mbps,average_mbps,min_mbps,median_mbps,max_mbps,threshold_mbps,threshold_pass,error_count\n");
    fflush(log);
}

static void csv_sample(FILE *log, const char *phase, const SpeedSamples *samples,
                       double elapsed, uint64_t total_bytes, uint64_t sample_bytes,
                       double sample_sec, double threshold, uint64_t errors)
{
    if (!log) return;
    double speed = sample_sec > 0.0 ? ((double)sample_bytes / 1000000.0) / sample_sec : 0.0;
    double avg = samples_average(samples);
    double med = samples_median(samples);
    int pass = threshold <= 0.0 || speed >= threshold;
    fprintf(log, "%s,%llu,%.6f,%llu,%llu,%.6f,%.6f,%.6f,%.6f,%.6f,%.6f,%.6f,%d,%llu\n",
            phase,
            (unsigned long long)samples->count,
            elapsed,
            (unsigned long long)total_bytes,
            (unsigned long long)sample_bytes,
            sample_sec,
            speed,
            avg,
            samples->count ? samples->min : 0.0,
            med,
            samples->count ? samples->max : 0.0,
            threshold,
            pass,
            (unsigned long long)errors);
    fflush(log);
}

static void csv_event(FILE *log, const char *event_name, DWORD error_code, uint64_t offset)
{
    if (!log) return;
    fprintf(log, "# event=%s,error_code=%lu,offset=%llu\n",
            event_name, (unsigned long)error_code, (unsigned long long)offset);
    fflush(log);
}

static BOOL open_test_file_for_write(const TestConfig *cfg, HANDLE *out)
{
    HANDLE h = CreateFileW(cfg->target_path,
                           GENERIC_WRITE,
                           FILE_SHARE_READ,
                           NULL,
                           CREATE_ALWAYS,
                           FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH,
                           NULL);
    if (h == INVALID_HANDLE_VALUE) {
        return FALSE;
    }
    *out = h;
    return TRUE;
}

static BOOL run_write_test(const TestConfig *cfg, TestState *st, FILE *log)
{
    HANDLE h = INVALID_HANDLE_VALUE;
    if (!open_test_file_for_write(cfg, &h)) {
        st->last_error = GetLastError();
        st->write_failed = TRUE;
        st->write_errors++;
        csv_event(log, "open_write_failed", st->last_error, 0);
        return FALSE;
    }

    void *buffer = VirtualAlloc(NULL, cfg->block_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (!buffer) {
        st->last_error = GetLastError();
        st->write_failed = TRUE;
        st->write_errors++;
        CloseHandle(h);
        csv_event(log, "buffer_allocation_failed", st->last_error, 0);
        return FALSE;
    }

    double wall_start = qpc_seconds();
    double sample_wall_start = wall_start;
    double sample_active = 0.0;
    uint64_t sample_bytes = 0;
    double current_mbps = 0.0;

    console_clear();
    console_cursor_visible(FALSE);

    while (st->bytes_written < cfg->target_bytes) {
        if (check_stop_key()) {
            st->user_stopped = TRUE;
            break;
        }

        double wall_now = qpc_seconds();
        st->wall_elapsed_sec = wall_now - wall_start;
        if (cfg->duration_limit_sec > 0.0 && st->wall_elapsed_sec >= cfg->duration_limit_sec) {
            st->user_stopped = TRUE;
            csv_event(log, "duration_limit_reached", 0, st->bytes_written);
            break;
        }

        uint64_t remaining = cfg->target_bytes - st->bytes_written;
        DWORD to_write = remaining < cfg->block_size ? (DWORD)remaining : cfg->block_size;
        to_write = (DWORD)align_down_u64(to_write, cfg->alignment);
        if (to_write == 0) break;

        fill_pattern(buffer, to_write, st->bytes_written);

        DWORD written = 0;
        double t0 = qpc_seconds();
        BOOL ok = WriteFile(h, buffer, to_write, &written, NULL);
        double t1 = qpc_seconds();
        double active = t1 - t0;

        st->write_active_sec += active;
        sample_active += active;

        if (!ok || written != to_write) {
            st->last_error = ok ? ERROR_WRITE_FAULT : GetLastError();
            st->write_failed = TRUE;
            st->write_errors++;
            if (written > 0) {
                st->bytes_written += written;
                sample_bytes += written;
            }
            csv_event(log, "write_failed_or_short_write", st->last_error, st->bytes_written);
            break;
        }

        st->bytes_written += written;
        sample_bytes += written;

        wall_now = qpc_seconds();
        st->wall_elapsed_sec = wall_now - wall_start;
        double sample_wall = wall_now - sample_wall_start;

        /* Device speed is based on time spent inside WriteFile, not pattern generation. */
        if (sample_wall >= SAMPLE_TARGET_SEC || st->bytes_written >= cfg->target_bytes) {
            current_mbps = sample_active > 0.0
                ? ((double)sample_bytes / 1000000.0) / sample_active : 0.0;
            if (!samples_add(&st->write_samples, current_mbps)) {
                st->last_error = ERROR_NOT_ENOUGH_MEMORY;
                st->write_failed = TRUE;
                st->write_errors++;
                csv_event(log, "statistics_allocation_failed", st->last_error, st->bytes_written);
                break;
            }
            csv_sample(log, "write", &st->write_samples, st->wall_elapsed_sec,
                       st->bytes_written, sample_bytes, sample_active,
                       cfg->min_required_mbps, st->write_errors);
            draw_write_ui(cfg, st, current_mbps, sample_bytes, sample_active);
            sample_wall_start = wall_now;
            sample_active = 0.0;
            sample_bytes = 0;
        }
    }

    /* Record a final partial sample if needed. */
    if (sample_bytes > 0 && sample_active > 0.0) {
        current_mbps = ((double)sample_bytes / 1000000.0) / sample_active;
        if (samples_add(&st->write_samples, current_mbps)) {
            csv_sample(log, "write", &st->write_samples, st->wall_elapsed_sec,
                       st->bytes_written, sample_bytes, sample_active,
                       cfg->min_required_mbps, st->write_errors);
        }
    }

    /* Explicitly flush metadata and any device-visible pending data before closing. */
    if (!FlushFileBuffers(h)) {
        DWORD e = GetLastError();
        st->last_error = e;
        st->write_failed = TRUE;
        st->write_errors++;
        csv_event(log, "flush_failed", e, st->bytes_written);
    }

    CloseHandle(h);
    VirtualFree(buffer, 0, MEM_RELEASE);
    st->wall_elapsed_sec = qpc_seconds() - wall_start;
    return !st->write_failed;
}

static BOOL run_verify_test(const TestConfig *cfg, TestState *st, FILE *log)
{
    if (st->bytes_written == 0) return FALSE;

    InterlockedExchange(&g_stop_requested, 0);

    HANDLE h = CreateFileW(cfg->target_path,
                           GENERIC_READ,
                           FILE_SHARE_READ | FILE_SHARE_WRITE,
                           NULL,
                           OPEN_EXISTING,
                           FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING | FILE_FLAG_SEQUENTIAL_SCAN,
                           NULL);
    if (h == INVALID_HANDLE_VALUE) {
        st->last_error = GetLastError();
        st->verify_failed = TRUE;
        st->verify_errors++;
        csv_event(log, "open_verify_failed", st->last_error, 0);
        return FALSE;
    }

    void *buffer = VirtualAlloc(NULL, cfg->block_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (!buffer) {
        st->last_error = GetLastError();
        st->verify_failed = TRUE;
        st->verify_errors++;
        CloseHandle(h);
        return FALSE;
    }

    double start = qpc_seconds();
    double sample_start = start;
    uint64_t sample_bytes = 0;
    double current_mbps = 0.0;
    console_clear();

    while (st->bytes_verified < st->bytes_written) {
        if (check_stop_key()) {
            st->user_stopped = TRUE;
            break;
        }

        uint64_t remaining = st->bytes_written - st->bytes_verified;
        DWORD to_read = remaining < cfg->block_size ? (DWORD)remaining : cfg->block_size;
        to_read = (DWORD)align_down_u64(to_read, cfg->alignment);
        if (to_read == 0) break;

        DWORD got = 0;
        BOOL ok = ReadFile(h, buffer, to_read, &got, NULL);
        if (!ok || got != to_read) {
            st->last_error = ok ? ERROR_READ_FAULT : GetLastError();
            st->verify_failed = TRUE;
            st->verify_errors++;
            csv_event(log, "read_failed_or_short_read", st->last_error, st->bytes_verified);
            break;
        }

        uint64_t bad_offset = 0;
        if (!verify_pattern(buffer, got, st->bytes_verified, &bad_offset)) {
            st->verify_failed = TRUE;
            st->mismatch_blocks++;
            csv_event(log, "data_mismatch", ERROR_CRC, bad_offset);
            /* Continue verifying to discover whether corruption is isolated or widespread. */
        }

        st->bytes_verified += got;
        sample_bytes += got;
        double now = qpc_seconds();
        st->verify_elapsed_sec = now - start;
        double sample_sec = now - sample_start;
        if (sample_sec >= SAMPLE_TARGET_SEC || st->bytes_verified >= st->bytes_written) {
            current_mbps = sample_sec > 0.0
                ? ((double)sample_bytes / 1000000.0) / sample_sec : 0.0;
            if (!samples_add(&st->verify_samples, current_mbps)) {
                st->verify_failed = TRUE;
                st->verify_errors++;
                break;
            }
            csv_sample(log, "verify", &st->verify_samples, st->verify_elapsed_sec,
                       st->bytes_verified, sample_bytes, sample_sec, 0.0, st->verify_errors);
            draw_verify_ui(cfg, st, current_mbps);
            sample_start = now;
            sample_bytes = 0;
        }
    }

    CloseHandle(h);
    VirtualFree(buffer, 0, MEM_RELEASE);
    st->verify_elapsed_sec = qpc_seconds() - start;
    return !st->verify_failed && st->bytes_verified == st->bytes_written;
}

static void write_summary(FILE *log, const TestConfig *cfg, const TestState *st)
{
    if (!log) return;
    size_t below = 0;
    if (cfg->min_required_mbps > 0.0) {
        for (size_t i = 0; i < st->write_samples.count; ++i)
            if (st->write_samples.values[i] < cfg->min_required_mbps) ++below;
    }
    fprintf(log, "# summary_begin\n");
    fprintf(log, "# bytes_written=%llu\n", (unsigned long long)st->bytes_written);
    fprintf(log, "# wall_elapsed_sec=%.6f\n", st->wall_elapsed_sec);
    fprintf(log, "# write_active_sec=%.6f\n", st->write_active_sec);
    fprintf(log, "# write_min_mbps=%.6f\n", st->write_samples.count ? st->write_samples.min : 0.0);
    fprintf(log, "# write_avg_sample_mbps=%.6f\n", samples_average(&st->write_samples));
    fprintf(log, "# write_median_mbps=%.6f\n", samples_median(&st->write_samples));
    fprintf(log, "# write_max_mbps=%.6f\n", st->write_samples.count ? st->write_samples.max : 0.0);
    fprintf(log, "# threshold_below_samples=%llu\n", (unsigned long long)below);
    fprintf(log, "# threshold_pass=%d\n", cfg->min_required_mbps <= 0.0 || below == 0);
    fprintf(log, "# write_errors=%llu\n", (unsigned long long)st->write_errors);
    fprintf(log, "# verification_requested=%d\n", cfg->verify_after_write);
    fprintf(log, "# bytes_verified=%llu\n", (unsigned long long)st->bytes_verified);
    fprintf(log, "# verification_mismatch_blocks=%llu\n", (unsigned long long)st->mismatch_blocks);
    fprintf(log, "# verification_errors=%llu\n", (unsigned long long)st->verify_errors);
    fprintf(log, "# user_stopped=%d\n", st->user_stopped);
    fprintf(log, "# summary_end\n");
    fflush(log);
}

static void print_final_summary(const TestConfig *cfg, const TestState *st)
{
    console_clear();
    console_cursor_visible(TRUE);

    char bytes[64], wall[32], active[32], verify[32];
    format_bytes(st->bytes_written, bytes, sizeof(bytes));
    format_duration(st->wall_elapsed_sec, wall, sizeof(wall));
    format_duration(st->write_active_sec, active, sizeof(active));
    format_duration(st->verify_elapsed_sec, verify, sizeof(verify));

    size_t below = 0;
    if (cfg->min_required_mbps > 0.0) {
        for (size_t i = 0; i < st->write_samples.count; ++i)
            if (st->write_samples.values[i] < cfg->min_required_mbps) ++below;
    }

    printf("================================================================================\n");
    printf(" %s - FINAL RESULT\n", APP_NAME);
    printf("================================================================================\n\n");
    printf("Written data       : %s\n", bytes);
    printf("Wall time          : %s\n", wall);
    printf("Active write time  : %s\n", active);
    printf("Write samples      : %llu\n", (unsigned long long)st->write_samples.count);
    printf("Minimum speed      : %.2f MB/s\n", st->write_samples.count ? st->write_samples.min : 0.0);
    printf("Average speed      : %.2f MB/s\n", samples_average(&st->write_samples));
    printf("Median speed       : %.2f MB/s\n", samples_median(&st->write_samples));
    printf("Maximum speed      : %.2f MB/s\n", st->write_samples.count ? st->write_samples.max : 0.0);

    if (cfg->min_required_mbps > 0.0) {
        printf("Required minimum   : %.2f MB/s\n", cfg->min_required_mbps);
        printf("Threshold result   : %s (%llu below-threshold samples)\n",
               below == 0 && st->write_samples.count > 0 ? "PASS" : "FAIL",
               (unsigned long long)below);
    }

    printf("Write errors       : %llu\n", (unsigned long long)st->write_errors);
    printf("Write status       : %s\n",
           st->write_failed ? "FAILED" : (st->user_stopped ? "STOPPED BY USER / LIMIT" : "COMPLETED"));

    if (cfg->verify_after_write) {
        printf("\nVerification time  : %s\n", verify);
        printf("Bytes verified     : %llu\n", (unsigned long long)st->bytes_verified);
        printf("Mismatch blocks    : %llu\n", (unsigned long long)st->mismatch_blocks);
        printf("Read errors        : %llu\n", (unsigned long long)st->verify_errors);
        printf("Data integrity     : %s\n",
               (!st->verify_failed && st->bytes_verified == st->bytes_written &&
                st->mismatch_blocks == 0) ? "PASS" : "FAIL / INCOMPLETE");
    }

    if (st->last_error != ERROR_SUCCESS) {
        printf("\nLast Windows error : %lu\n", (unsigned long)st->last_error);
    }
    printf("\nCSV log            : ");
    wprintf(L"%ls\n", cfg->log_path);
    printf("Test file          : ");
    wprintf(L"%ls\n", cfg->target_path);
    printf("================================================================================\n");
}

static BOOL configure_test(TestConfig *cfg)
{
    memset(cfg, 0, sizeof(*cfg));

    wprintf(L"%hs v%hs\n", APP_NAME, APP_VERSION);
    wprintf(L"Native Windows 10/11 sustained storage write test\n\n");
    wprintf(L"WARNING: This creates a very large test file and can fill the selected drive.\n");
    wprintf(L"It does not format the drive and does not intentionally modify other files.\n\n");

    if (!read_line_w(L"Target directory (example E:\\): ", cfg->target_dir, _countof(cfg->target_dir))) {
        return FALSE;
    }
    if (cfg->target_dir[0] == L'\0') return FALSE;
    normalize_dir_path(cfg->target_dir, _countof(cfg->target_dir));

    DWORD attrs = GetFileAttributesW(cfg->target_dir);
    if (attrs == INVALID_FILE_ATTRIBUTES || !(attrs & FILE_ATTRIBUTE_DIRECTORY)) {
        print_win_error(L"Target directory is not accessible", GetLastError());
        return FALSE;
    }

    wchar_t filename[MAX_PATH_INPUT] = L"casinolove-drive-test.bin";
    wchar_t line[MAX_PATH_INPUT];
    if (!read_line_w(L"Test filename [casinolove-drive-test.bin]: ", line, _countof(line))) return FALSE;
    if (line[0] != L'\0') wcsncpy(filename, line, _countof(filename) - 1);
    if (!valid_simple_filename(filename)) {
        fwprintf(stderr, L"Invalid filename. Use a filename only, without path separators.\n");
        return FALSE;
    }
    wcsncpy(cfg->target_file, filename, _countof(cfg->target_file) - 1);
    if (!combine_path(cfg->target_dir, cfg->target_file, cfg->target_path, _countof(cfg->target_path))) {
        fwprintf(stderr, L"Target path is too long.\n");
        return FALSE;
    }

    DWORD existing = GetFileAttributesW(cfg->target_path);
    if (existing != INVALID_FILE_ATTRIBUTES) {
        wprintf(L"\nThe test file already exists and will be overwritten:\n  %ls\n", cfg->target_path);
        if (!prompt_yes_no(L"Overwrite it? [y/N]: ", FALSE)) return FALSE;
    }

    uint64_t free_bytes = 0;
    DWORD logical_sector = 0;
    if (!get_volume_info_for_path(cfg->target_dir, cfg->volume_root, _countof(cfg->volume_root),
                                  &free_bytes, &logical_sector)) {
        print_win_error(L"Could not query target volume", GetLastError());
        return FALSE;
    }

    cfg->logical_sector = logical_sector;
    cfg->physical_sector = query_physical_sector_size(cfg->volume_root);
    if (cfg->physical_sector == 0) cfg->physical_sector = logical_sector;
    cfg->alignment = choose_alignment(cfg->logical_sector, cfg->physical_sector);
    query_filesystem_name(cfg->volume_root, cfg->filesystem, _countof(cfg->filesystem));
    uint64_t block = DEFAULT_BLOCK_SIZE;
    block = align_down_u64(block, cfg->alignment);
    if (block < cfg->alignment) block = cfg->alignment;
    cfg->block_size = (DWORD)block;

    char free_s[64];
    format_bytes(free_bytes, free_s, sizeof(free_s));
    wprintf(L"\nVolume root        : %ls\n", cfg->volume_root);
    printf("Available free     : %s\n", free_s);
    wprintf(L"Filesystem         : %ls\n", cfg->filesystem);
    printf("Logical sector     : %lu bytes\n", (unsigned long)cfg->logical_sector);
    printf("Physical sector    : %lu bytes\n", (unsigned long)cfg->physical_sector);
    printf("I/O alignment      : %lu bytes\n", (unsigned long)cfg->alignment);
    printf("Write block        : %lu bytes\n\n", (unsigned long)cfg->block_size);

    double size_gib = prompt_double(L"Test size in GiB [0 = maximum safe free space]: ", 0.0, 0.0);
    uint64_t max_safe = free_bytes > SAFETY_RESERVE ? free_bytes - SAFETY_RESERVE : 0;
    max_safe = align_down_u64(max_safe, cfg->alignment);
    if (max_safe == 0) {
        fwprintf(stderr, L"Not enough free space after the 256 MiB safety reserve.\n");
        return FALSE;
    }

    if (size_gib <= 0.0) cfg->target_bytes = max_safe;
    else {
        long double requested = (long double)size_gib * 1024.0L * 1024.0L * 1024.0L;
        if (requested > (long double)UINT64_MAX) {
            fwprintf(stderr, L"Requested size is too large.\n");
            return FALSE;
        }
        cfg->target_bytes = align_down_u64((uint64_t)requested, cfg->alignment);
        if (cfg->target_bytes > max_safe) {
            char max_s[64];
            format_bytes(max_safe, max_s, sizeof(max_s));
            printf("Requested size exceeds safe free space. Maximum is %s.\n", max_s);
            return FALSE;
        }
    }

    cfg->duration_limit_sec = prompt_double(
        L"Optional duration limit in minutes [0 = disabled]: ", 0.0, 0.0) * 60.0;
    cfg->min_required_mbps = prompt_double(
        L"Required minimum sustained write speed in MB/s [0 = none]: ", 100.0, 0.0);

    cfg->verify_after_write = prompt_yes_no(
        L"Verify the written file byte-for-byte after the write test? [Y/n]: ", TRUE);

    time_t now = time(NULL);
    struct tm lt;
    localtime_s(&lt, &now);
    wchar_t default_log[MAX_PATH_INPUT];
    _snwprintf(default_log, _countof(default_log),
               L"drive-test-%04d%02d%02d-%02d%02d%02d.csv",
               lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday,
               lt.tm_hour, lt.tm_min, lt.tm_sec);

    wprintf(L"Log path [%ls]: ", default_log);
    fflush(stdout);
    if (!fgetws(line, _countof(line), stdin)) return FALSE;
    trim_newline_w(line);
    if (line[0] == L'\0') wcsncpy(cfg->log_path, default_log, _countof(cfg->log_path) - 1);
    else wcsncpy(cfg->log_path, line, _countof(cfg->log_path) - 1);

    /* If log is relative, GetFullPathNameW makes volume comparison meaningful. */
    wchar_t full_log[MAX_PATH_INPUT];
    DWORD len = GetFullPathNameW(cfg->log_path, _countof(full_log), full_log, NULL);
    if (len > 0 && len < _countof(full_log)) {
        wcsncpy(cfg->log_path, full_log, _countof(cfg->log_path) - 1);
    }

    if (same_volume(cfg->target_path, cfg->log_path)) {
        wprintf(L"\nWARNING: The CSV log is on the SAME VOLUME as the test file.\n");
        wprintf(L"That adds extra writes and can slightly distort the measurement.\n");
        if (!prompt_yes_no(L"Continue with this log location? [y/N]: ", FALSE)) return FALSE;
    }

    cfg->delete_after_test = prompt_yes_no(
        L"Delete the test file automatically after all tests? [y/N]: ", FALSE);

    char target_s[64];
    format_bytes(cfg->target_bytes, target_s, sizeof(target_s));
    wprintf(L"\nTarget file : %ls\n", cfg->target_path);
    printf("Test size   : %s\n", target_s);
    printf("Threshold   : %.2f MB/s\n", cfg->min_required_mbps);
    printf("Verification: %s\n", cfg->verify_after_write ? "yes" : "no");
    wprintf(L"Log         : %ls\n\n", cfg->log_path);

    if (!prompt_yes_no(L"START TEST? [y/N]: ", FALSE)) return FALSE;
    return TRUE;
}

int main(void)
{
    SetConsoleOutputCP(CP_UTF8);
    SetConsoleCP(CP_UTF8);
    g_console = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleCtrlHandler(console_ctrl_handler, TRUE);

    TestConfig cfg;
    TestState st;
    memset(&st, 0, sizeof(st));

    if (!configure_test(&cfg)) {
        printf("\nTest cancelled or configuration failed.\n");
        return 1;
    }

    FILE *log = _wfopen(cfg.log_path, L"wb");
    if (!log) {
        fwprintf(stderr, L"Could not create log file: %ls\n", cfg.log_path);
        return 2;
    }
    csv_header(log, &cfg);

    BOOL write_ok = run_write_test(&cfg, &st, log);
    if (!write_ok && st.last_error != ERROR_SUCCESS) {
        console_cursor_visible(TRUE);
        print_win_error(L"Write test error", st.last_error);
    }

    BOOL verify_ok = TRUE;
    if (cfg.verify_after_write && st.bytes_written > 0 && !st.write_failed) {
        /* Give the operator an intentional transition point before the read pass. */
        Sleep(250);
        verify_ok = run_verify_test(&cfg, &st, log);
        if (!verify_ok && st.last_error != ERROR_SUCCESS) {
            console_cursor_visible(TRUE);
            print_win_error(L"Verification error", st.last_error);
        }
    }

    write_summary(log, &cfg, &st);
    fclose(log);
    print_final_summary(&cfg, &st);

    if (cfg.delete_after_test) {
        if (DeleteFileW(cfg.target_path)) {
            printf("\nTest file deleted successfully.\n");
        } else {
            DWORD e = GetLastError();
            print_win_error(L"Could not delete test file", e);
            printf("The test result is still valid; delete the file manually when convenient.\n");
        }
    } else if (GetFileAttributesW(cfg.target_path) != INVALID_FILE_ATTRIBUTES) {
        printf("\nTest file kept on disk as requested.\n");
    }

    samples_free(&st.write_samples);
    samples_free(&st.verify_samples);
    console_cursor_visible(TRUE);
    SetConsoleCtrlHandler(console_ctrl_handler, FALSE);

    if (!write_ok || !verify_ok || st.write_errors || st.verify_errors || st.mismatch_blocks) return 3;
    return 0;
}
