/**
 * @file test_dmabuf_integration.c
 * @brief DMA-BUF Integration Test
 *
 * Multi-Vendor GPU Aggregation Layer for Linux
 *
 * Validates the DMA-BUF allocation → export → import round-trip:
 *   1. Allocate a DMA-BUF via mvgal_dmabuf_allocate
 *   2. Write a deterministic pattern to CPU mapping
 *   3. Export the buffer's FD
 *   4. Import the FD into a second buffer
 *   5. Verify data integrity on both sides
 *   6. Free both buffers
 *
 * This test works without a physical GPU — DMA-BUF allocation falls
 * back to memfd_create when the kernel DMA heap is unavailable.
 *
 * Marked REQUIRES_KERNEL_MODULE in CI: full test requires /dev/mvgal0
 * for the UAPI-backed dma-buf path, but the fallback path validates
 * the userspace DMA-BUF implementation regardless.
 */

#include "mvgal/mvgal.h"
#include "mvgal/mvgal_memory.h"
#include "mvgal/mvgal_log.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>

/* Internal header for mvgal_dmabuf_allocate / export / import */
#include "memory_internal.h"

#ifndef MVGAL_DMABUF_TEST_SIZE
#define MVGAL_DMABUF_TEST_SIZE (64U * 1024U) /* 64 KB */
#endif

static int tests_passed = 0;
static int tests_failed = 0;

#define TEST_ASSERT(cond, msg)                                          \
    do {                                                                \
        if (cond) {                                                     \
            tests_passed++;                                             \
            MVGAL_LOG_INFO("  PASS: %s", msg);                          \
        } else {                                                        \
            tests_failed++;                                             \
            MVGAL_LOG_ERROR("  FAIL: %s", msg);                         \
        }                                                               \
    } while (0)

/**
 * @brief Fill a buffer with a deterministic test pattern.
 */
static void fill_pattern(uint8_t *buf, size_t size, uint32_t seed) {
    for (size_t i = 0; i < size; i++) {
        buf[i] = (uint8_t)((seed + i) & 0xFF);
    }
}

/**
 * @brief Verify a buffer matches the expected pattern.
 */
static int check_pattern(const uint8_t *buf, size_t size, uint32_t seed) {
    for (size_t i = 0; i < size; i++) {
        if (buf[i] != (uint8_t)((seed + i) & 0xFF)) {
            MVGAL_LOG_ERROR("  mismatch at offset %zu: got 0x%02x, expected 0x%02x",
                           i, buf[i], (uint8_t)((seed + i) & 0xFF));
            return 0;
        }
    }
    return 1;
}

/**
 * @brief Test 1: DMA-BUF allocate → write → read back
 */
static int test_alloc_write_read(void) {
    int fd = -1;
    void *ptr = NULL;
    size_t size = MVGAL_DMABUF_TEST_SIZE;
    uint32_t pattern_seed = 0xDEADBEEF;

    mvgal_error_t err = mvgal_dmabuf_allocate(
        size, 0,
        MVGAL_MEMORY_FLAG_ZERO_INITIALIZED | MVGAL_MEMORY_FLAG_CPU_CACHED,
        0xFFFFFFFF, /* all GPUs */
        &fd, &ptr
    );

    if (err != MVGAL_SUCCESS) {
        TEST_ASSERT(0, "mvgal_dmabuf_allocate");
        return 0;
    }
    TEST_ASSERT(1, "mvgal_dmabuf_allocate");
    TEST_ASSERT(ptr != NULL, "allocated pointer is not NULL");
    TEST_ASSERT(fd >= 0, "allocated fd is valid");

    /* Verify zero-initialized */
    {
        int zero = 1;
        const uint8_t *bytes = (const uint8_t *)ptr;
        for (size_t i = 0; i < size && zero; i++) {
            if (bytes[i] != 0) zero = 0;
        }
        TEST_ASSERT(zero, "buffer is zero-initialized");
    }

    /* Write pattern */
    fill_pattern((uint8_t *)ptr, size, pattern_seed);

    /* Read back */
    TEST_ASSERT(check_pattern((const uint8_t *)ptr, size, pattern_seed),
                "write-then-read pattern matches");

    /* Clean up */
    mvgal_dmabuf_free(fd, ptr, size);
    TEST_ASSERT(1, "mvgal_dmabuf_free");
    return 1;
}

/**
 * @brief Test 2: DMA-BUF allocate → export → import → verify
 */
static int test_export_import_roundtrip(void) {
    int fd1 = -1, fd2 = -1;
    void *ptr1 = NULL, *ptr2 = NULL;
    size_t size = MVGAL_DMABUF_TEST_SIZE;
    uint32_t pattern_seed = 0xCAFEBABE;

    /* Step 1: allocate first buffer */
    mvgal_error_t err = mvgal_dmabuf_allocate(
        size, 0,
        MVGAL_MEMORY_FLAG_ZERO_INITIALIZED,
        0xFFFFFFFF, &fd1, &ptr1
    );
    TEST_ASSERT(err == MVGAL_SUCCESS, "allocate buffer A");
    if (err != MVGAL_SUCCESS) return 0;

    /* Step 2: write pattern to A */
    fill_pattern((uint8_t *)ptr1, size, pattern_seed);

    /* Step 3: export fd1 → create a clone FD for import */
    /* (The memfd allocation already gives us a sharable FD.) */
    fd2 = dup(fd1);
    TEST_ASSERT(fd2 >= 0, "dup fd for import");

    /* Step 4: import fd2 as a new mapping */
    struct mvgal_buffer *imported = NULL;
    err = mvgal_dmabuf_import(fd2, size, MVGAL_MEMORY_FLAG_CPU_CACHED, &imported);
    TEST_ASSERT(err == MVGAL_SUCCESS, "mvgal_dmabuf_import");
    TEST_ASSERT(imported != NULL, "imported buffer is not NULL");

    if (err == MVGAL_SUCCESS && imported != NULL) {
        /* Step 5: verify imported data matches pattern */
        ptr2 = imported->host_ptr;
        TEST_ASSERT(ptr2 != NULL, "imported host_ptr is not NULL");
        if (ptr2) {
            TEST_ASSERT(check_pattern((const uint8_t *)ptr2, size, pattern_seed),
                        "imported buffer data matches pattern");

            /* Step 6: modify imported and verify original sees change
             * (shared memory — both point to same pages) */
            ((uint8_t *)ptr2)[0] = 0xFF;
            TEST_ASSERT(((uint8_t *)ptr1)[0] == 0xFF,
                        "coherent write through import");
        }
    }

    /* Step 7: clean up */
    if (imported) {
        mvgal_buffer_free_internal(imported);
    }
    mvgal_dmabuf_free(fd1, ptr1, size);
    /* fd2 is now owned by the imported buffer or closed by us */
    if (fd2 >= 0 && !imported) close(fd2);

    TEST_ASSERT(1, "export → import round-trip complete");
    return 1;
}

/**
 * @brief Test 3: Host-RAM staging fallback copy
 *
 * Allocate two DMA-BUF buffers, write pattern to A, copy to B via memcpy
 * (simulating the P2P copy fallback), verify B matches.
 */
static int test_staging_copy(void) {
    int fd_a = -1, fd_b = -1;
    void *ptr_a = NULL, *ptr_b = NULL;
    size_t size = MVGAL_DMABUF_TEST_SIZE;
    uint32_t pattern_seed = 0x12345678;

    mvgal_error_t err;

    /* Allocate A */
    err = mvgal_dmabuf_allocate(size, 0, MVGAL_MEMORY_FLAG_ZERO_INITIALIZED,
                                0xFFFFFFFF, &fd_a, &ptr_a);
    TEST_ASSERT(err == MVGAL_SUCCESS, "allocate source buffer");
    if (err != MVGAL_SUCCESS) return 0;

    /* Allocate B */
    err = mvgal_dmabuf_allocate(size, 0, MVGAL_MEMORY_FLAG_ZERO_INITIALIZED,
                                0xFFFFFFFF, &fd_b, &ptr_b);
    TEST_ASSERT(err == MVGAL_SUCCESS, "allocate destination buffer");
    if (err != MVGAL_SUCCESS) {
        mvgal_dmabuf_free(fd_a, ptr_a, size);
        return 0;
    }

    /* Write pattern to A */
    fill_pattern((uint8_t *)ptr_a, size, pattern_seed);

    /* memcpy A → B (staging copy) */
    memcpy(ptr_b, ptr_a, size);

    /* Verify B */
    TEST_ASSERT(check_pattern((const uint8_t *)ptr_b, size, pattern_seed),
                "staging copy data matches");

    /* Clean up */
    mvgal_dmabuf_free(fd_a, ptr_a, size);
    mvgal_dmabuf_free(fd_b, ptr_b, size);
    TEST_ASSERT(1, "host-RAM staging copy test complete");
    return 1;
}

int main(void) {
    fprintf(stdout, "\n========================================================\n");
    fprintf(stdout, "  MVGAL DMA-BUF Integration Test\n");
    fprintf(stdout, "========================================================\n\n");

    mvgal_error_t err = mvgal_init(0);
    if (err != MVGAL_SUCCESS) {
        fprintf(stderr, "mvgal_init() failed: %d\n", err);
        return EXIT_FAILURE;
    }
    fprintf(stdout, "  mvgal_init() OK\n\n");

    /* Test 1: Basic allocate → write → read */
    fprintf(stdout, "--- Test 1: DMA-BUF Allocate/Write/Read ---\n");
    test_alloc_write_read();
    fprintf(stdout, "\n");

    /* Test 2: Export → Import round-trip */
    fprintf(stdout, "--- Test 2: DMA-BUF Export/Import Round-trip ---\n");
    test_export_import_roundtrip();
    fprintf(stdout, "\n");

    /* Test 3: Staging copy */
    fprintf(stdout, "--- Test 3: Host-RAM Staging Copy ---\n");
    test_staging_copy();
    fprintf(stdout, "\n");

    mvgal_shutdown();

    /* Summary */
    fprintf(stdout, "========================================================\n");
    fprintf(stdout, "  Results: %d passed, %d failed\n",
            tests_passed, tests_failed);
    fprintf(stdout, "========================================================\n\n");

    return (tests_failed > 0) ? EXIT_FAILURE : EXIT_SUCCESS;
}
