/* ----------------------------------------------
*/
#include "afpcmd.h"
#include "afphelper.h"
#include "testhelper.h"

/* ------------------------- */
STATIC void catsearch_ext_send_raw(uint16_t f_bitmap, uint16_t d_bitmap,
                                   uint32_t rbitmap, uint16_t spec1_len,
                                   const void *spec1, size_t spec1_payload_len,
                                   uint16_t spec2_len, const void *spec2,
                                   size_t spec2_payload_len)
{
    uint16_t vol = VolID;
    uint16_t bitmap;
    uint16_t net_len;
    uint32_t temp;
    uint32_t net_rbitmap;
    char pos[16];
    int ofs;
    DSI *dsi;
    dsi = &Conn->dsi;
    memset(pos, 0, sizeof(pos));
    SendInit(dsi);
    ofs = 0;
    dsi->commands[ofs++] = AFP_CATSEARCH_EXT;
    dsi->commands[ofs++] = 0;
    memcpy(dsi->commands + ofs, &vol, sizeof(vol));
    ofs += sizeof(vol);
    temp = htonl(10);
    memcpy(dsi->commands + ofs, &temp, sizeof(temp));
    ofs += sizeof(temp);
    temp = 0;
    memcpy(dsi->commands + ofs, &temp, sizeof(temp));
    ofs += sizeof(temp);
    memcpy(dsi->commands + ofs, pos, sizeof(pos));
    ofs += sizeof(pos);
    bitmap = htons(f_bitmap);
    memcpy(dsi->commands + ofs, &bitmap, sizeof(bitmap));
    ofs += sizeof(bitmap);
    bitmap = htons(d_bitmap);
    memcpy(dsi->commands + ofs, &bitmap, sizeof(bitmap));
    ofs += sizeof(bitmap);
    net_rbitmap = htonl(rbitmap);
    memcpy(dsi->commands + ofs, &net_rbitmap, sizeof(net_rbitmap));
    ofs += sizeof(net_rbitmap);
    net_len = htons(spec1_len);
    memcpy(dsi->commands + ofs, &net_len, sizeof(net_len));
    ofs += sizeof(net_len);

    if (spec1_payload_len != 0) {
        memcpy(dsi->commands + ofs, spec1, spec1_payload_len);
        ofs += spec1_payload_len;
    }

    net_len = htons(spec2_len);
    memcpy(dsi->commands + ofs, &net_len, sizeof(net_len));
    ofs += sizeof(net_len);

    if (spec2_payload_len != 0) {
        memcpy(dsi->commands + ofs, spec2, spec2_payload_len);
        ofs += spec2_payload_len;
    }

    SetLen(dsi, ofs);
    dsi_stream_send(dsi, dsi->commands, dsi->datalen);
    dsi_data_receive(dsi);
}

/* ------------------------- */
STATIC void test227()
{
    uint16_t bitmap = (1 << FILPBIT_ATTR);
    uint16_t bitmap2;
    char *name = "t227 file.txt";
    uint16_t vol = VolID;
    uint32_t temp;
    const DSI *dsi;
    char pos[16];
    int  ofs =  3 * sizeof(uint16_t);
    struct afp_filedir_parms filedir = { 0 };
    struct afp_filedir_parms filedir2;
    unsigned int ret;
    ENTER_TEST
    dsi = &Conn->dsi;
    memset(pos, 0, sizeof(pos));
    memset(&filedir, 0, sizeof(filedir));
    filedir.attr = 0x01a0;			/* various lock attributes */
    ret = FPCatSearchExt(Conn, vol, 10, pos, 0,  /* d_bitmap*/ 0, bitmap, &filedir,
                         &filedir);

    if (Conn->afp_version < 30) {
        if (htonl(AFPERR_NOOP) != ret) {
            test_failed();
        } else {
            test_skipped(T_AFP3);
        }

        goto test_exit;
    }

    if (htonl(AFPERR_BITMAP) != ret) {
        test_failed();
    }

    if (FPCreateFile(Conn, vol, 0, DIRDID_ROOT, name)) {
        test_nottested();
        goto test_exit;
    }

    ret = FPCatSearchExt(Conn, vol, 10, pos, 0x42,  /* d_bitmap*/ 0, bitmap,
                         &filedir, &filedir);

    if (ret != htonl(AFPERR_EOF)) {
        test_failed();
    }

    memcpy(&temp, dsi->data + 20, sizeof(temp));
    temp = ntohl(temp);

    if (temp) {
        if (!Quiet) {
            fprintf(stdout, "\tFAILED want 0 get %d\n", temp);
        }

        test_failed();
    }

    filedir.isdir = 0;
    afp_filedir_unpack(Conn, &filedir, dsi->data + ofs, bitmap, 0);
    filedir.attr = 0x01a0 | ATTRBIT_SETCLR ;
    FAIL(FPSetFileParams(Conn, vol, DIRDID_ROOT, name, bitmap, &filedir))
    memset(&filedir, 0, sizeof(filedir));
    /* ------------------- */
    filedir.attr = 0x01a0;			/* lock attributes */
    ret  = FPCatSearchExt(Conn, vol, 10, pos, 0x42,  /* d_bitmap*/ 0, bitmap,
                          &filedir, &filedir);

    if (ret != htonl(AFPERR_EOF)) {
        test_failed();
    }

    memcpy(&temp, dsi->data + 20, sizeof(temp));
    temp = ntohl(temp);

    if (temp != 1) {
        if (!Quiet) {
            fprintf(stdout, "\tFAILED want 1 get %d\n", temp);
        }

        test_failed();
    }

    /* ------------------- */
    filedir.attr = 0x0100;			/* lock attributes */
    ret  = FPCatSearchExt(Conn, vol, 10, pos, 0x42,  /* d_bitmap*/ 0, bitmap,
                          &filedir, &filedir);

    if (ret != htonl(AFPERR_EOF)) {
        test_failed();
    }

    memcpy(&temp, dsi->data + 20, sizeof(temp));
    temp = ntohl(temp);

    if (temp != 1) {
        if (!Quiet) {
            fprintf(stdout, "\tFAILED want 1 get %d\n", temp);
        }

        test_failed();
    }

#if 1
    /* -------------------- */
    memset(&filedir, 0, sizeof(filedir));
    memset(&filedir2, 0, sizeof(filedir2));
    filedir.lname = "Data";
    ret  = FPCatSearchExt(Conn, vol, 10, pos, 0x42,  0x42,
                          0x80000000UL | (1 << FILPBIT_LNAME), &filedir, &filedir2);

    while (!ret) {
        memcpy(pos, dsi->data, 16);
        ret  = FPCatSearchExt(Conn, vol, 10, pos, 0x42,  0x42,
                              0x80000000UL | (1 << FILPBIT_LNAME), &filedir, &filedir2);
    }

    /* -------------------- */
    memset(pos, 0, sizeof(pos));
    ret  = FPCatSearchExt(Conn, vol, 10, pos, 0x42,  0x42,
                          0x80000000UL | (1 << FILPBIT_LNAME), &filedir, &filedir2);

    if (!ret) {
        memcpy(pos, dsi->data, 16);
        ret  = FPCatSearchExt(Conn, vol, 10, pos, 0x42,  0x42,
                              0x80000000UL | (1 << FILPBIT_LNAME), &filedir, &filedir2);
    }

    /* -------------------- */
    memset(pos, 0, sizeof(pos));
    filedir.lname = "test";
    ret  = FPCatSearchExt(Conn, vol, 10, pos, 0x42,  0x42,
                          0x80000000UL | (1 << FILPBIT_LNAME), &filedir, &filedir2);

    if (!ret) {
        memcpy(pos, dsi->data, 16);
        ret  = FPCatSearchExt(Conn, vol, 10, pos, 0x42,  0x42,
                              0x80000000UL | (1 << FILPBIT_LNAME), &filedir, &filedir2);
    }

    /* -------------------- */
    memset(pos, 0, sizeof(pos));
    filedir.utf8_name = "test";
    bitmap2 = (1 << FILPBIT_PDINFO) | (1 << FILPBIT_PDID);
    ret  = FPCatSearchExt(Conn, vol, 10, pos, bitmap2,  bitmap2,
                          0x80000000UL | (1 << FILPBIT_PDINFO), &filedir, &filedir2);

    while (!ret) {
        memcpy(pos, dsi->data, 16);
        ret  = FPCatSearchExt(Conn, vol, 10, pos, bitmap2,  bitmap2,
                              0x80000000UL | (1 << FILPBIT_PDINFO), &filedir, &filedir2);
    }

#endif
    /* -------------------- */
    memset(&filedir, 0, sizeof(filedir));
    filedir.attr = 0x01a0;
    FAIL(FPSetFileParams(Conn, vol, DIRDID_ROOT, name, bitmap, &filedir))
    FAIL(FPDelete(Conn, vol, DIRDID_ROOT, name))
test_exit:
    exit_test("FPCatSearchExt:test227: Catalog search");
}

/* test529: Catalog search with concurrent renames
 *
 * Tests FPCatSearchExt robustness when items are renamed during search iteration.
 * This validates dircache coherency under catalog search stress, important for
 * multi-user environments where catalog search is actively used.
 *
 * Scenario:
 * 1. Client 1 creates multiple files/dirs with searchable metadata (specific finder types)
 * 2. Client 1 starts FPCatSearchExt to find items matching criteria
 * 3. Client 2 renames some matching items while search in progress
 * 4. Client 1 continues iterating through search results
 * 5. Verify: No crashes, no duplicates, results consistent despite renames
 */
STATIC void test529()
{
    char base_name[256];
    char renamed_name[256];
    char *dir_name = "t529_catsearch_test";
    uint16_t vol = VolID;
    uint16_t vol2 = 0;
    uint16_t f_bitmap;
    uint16_t d_bitmap;
    uint32_t rbitmap;
    int dir = 0;
    int i;
    unsigned int ret;
    char pos[16];
    uint32_t match_count = 0;
    uint32_t temp;
    const DSI *dsi;
    struct afp_filedir_parms filedir = { 0 };
    struct afp_filedir_parms filedir2;
    int iteration = 0;
    ENTER_TEST

    if (Conn->afp_version < 30) {
        test_skipped(T_AFP3);
        goto test_exit;
    }

    if (Path[0] == '\0') {
        test_skipped(T_PATH);
        goto test_exit;
    }

    if (!Conn2) {
        test_skipped(T_CONN2);
        goto test_exit;
    }

    dsi = &Conn->dsi;
    f_bitmap = (1 << FILPBIT_LNAME) | (1 << FILPBIT_FNUM) | (1 << FILPBIT_FINFO);
    d_bitmap = (1 << DIRPBIT_LNAME) | (1 << DIRPBIT_DID);

    /************************************
     * Step 1: Client 1 creates directory with searchable items
     ************************************/
    if (!Quiet) {
        fprintf(stdout,
                "\t  Step 1: Client 1 creates directory with 20 searchable files\n");
    }

    dir = FPCreateDir(Conn, vol, DIRDID_ROOT, dir_name);

    if (!dir) {
        test_nottested();
        goto test_exit;
    }

    /* Create 20 files with PDF finder info (searchable by PDINFO) */
    for (i = 1; i <= 20; i++) {
        snprintf(base_name, sizeof(base_name), "search_file_%02d.pdf", i);

        if (FPCreateFile(Conn, vol, 0, dir, base_name)) {
            if (!Quiet) {
                fprintf(stdout, "\tFAILED to create file %s\n", base_name);
            }

            test_nottested();
            goto fin;
        }

        /* Set finder info to PDF type */
        memset(&filedir, 0, sizeof(filedir));
        memset(&filedir2, 0, sizeof(filedir2));
        filedir.isdir = 0;
        memcpy(filedir.finder_info, "PDF CARO", 8);

        if (FPSetFileParams(Conn, vol, dir, base_name, (1 << FILPBIT_FINFO),
                            &filedir) && !Quiet) {
            fprintf(stdout, "\tFAILED to set finder info for %s\n", base_name);
        }

        /* Continue anyway, not critical */
    }

    if (!Quiet && Verbose) {
        fprintf(stdout, "\t    Created 20 files with PDF finder info\n");
    }

    /************************************
     * Step 2: Client 1 starts catalog search for PDF files
     ************************************/
    if (!Quiet) {
        fprintf(stdout, "\t  Step 2: Client 1 starts catalog search for PDF files\n");
    }

    memset(pos, 0, sizeof(pos));
    memset(&filedir, 0, sizeof(filedir));
    memset(&filedir2, 0, sizeof(filedir2));
    filedir.utf8_name = "search";
    rbitmap = 0x80000000UL | (1 << FILPBIT_PDINFO);
    ret = FPCatSearchExt(Conn, vol, 10, pos, f_bitmap, d_bitmap, rbitmap, &filedir,
                         &filedir2);

    /* Get initial result count from response */
    if (ret == 0 || ret == ntohl(AFPERR_EOF)) {
        memcpy(&temp, dsi->data + 20, sizeof(temp));
        match_count = ntohl(temp);

        if (!Quiet && Verbose) {
            fprintf(stdout, "\t    Initial search found %d matches\n", match_count);
        }
    }

    /************************************
     * Step 3: Client 2 opens volume and renames items 8-12 during search
     ************************************/
    if (!Quiet) {
        fprintf(stdout,
                "\t  Step 3: Client 2 renames items 8-12 while search in progress\n");
    }

    vol2 = FPOpenVol(Conn2, Vol);

    if (vol2 == 0xffff) {
        if (!Quiet) {
            fprintf(stdout, "\tFAILED Client 2 could not open volume\n");
        }

        test_nottested();
        goto fin;
    }

    /* Rename files 8-12 (middle of the result set) */
    for (i = 8; i <= 12; i++) {
        snprintf(base_name, sizeof(base_name), "search_file_%02d.pdf", i);
        snprintf(renamed_name, sizeof(renamed_name), "renamed_search_%02d.pdf", i);
        FPRename(Conn2, vol2, dir, base_name, renamed_name);  /* Best effort */
    }

    if (!Quiet && Verbose) {
        fprintf(stdout, "\t    Renamed items 8-12\n");
    }

    /************************************
     * Step 4: Client 1 continues iterating through search results
     ************************************/
    if (!Quiet) {
        fprintf(stdout,
                "\t  Step 4: Client 1 continues iterating through search results\n");
    }

    /* Continue iteration if we got results */
    iteration = 1;

    while (ret == 0
            && iteration < 50) {  /* Safety limit to prevent infinite loop */
        memcpy(pos, dsi->data, 16);
        ret = FPCatSearchExt(Conn, vol, 10, pos, f_bitmap, d_bitmap, rbitmap, &filedir,
                             &filedir2);
        iteration++;

        if (!Quiet && Verbose) {
            fprintf(stdout, "\t    Iteration %d, ret=%d\n", iteration, ntohl(ret));
        }

        /* After 3 iterations, have Client 2 rename more items */
        if (iteration == 3) {
            if (!Quiet && Verbose) {
                fprintf(stdout, "\t    Client 2 renames items 15-17\n");
            }

            for (i = 15; i <= 17; i++) {
                snprintf(base_name, sizeof(base_name), "search_file_%02d.pdf", i);
                snprintf(renamed_name, sizeof(renamed_name), "renamed_search_%02d.pdf", i);
                FPRename(Conn2, vol2, dir, base_name, renamed_name);  /* Best effort */
            }
        }
    }

    if (ret != ntohl(AFPERR_EOF) && ret != 0) {
        if (!Quiet) {
            fprintf(stdout,
                    "\tFAILED search iteration ended with unexpected error: %d (%s)\n",
                    ntohl(ret), afp_error(ret));
        }

        test_failed();
        goto fin;
    }

    if (!Quiet && Verbose) {
        fprintf(stdout, "\t    Search completed after %d iterations\n", iteration);
    }

    /************************************
     * Step 5: Re-run search to verify consistency
     ************************************/
    if (!Quiet) {
        fprintf(stdout, "\t  Step 5: Re-run search to verify no corruption\n");
    }

    memset(pos, 0, sizeof(pos));
    ret = FPCatSearchExt(Conn, vol, 10, pos, f_bitmap, d_bitmap, rbitmap, &filedir,
                         &filedir2);

    if (ret != 0 && ret != ntohl(AFPERR_EOF)) {
        if (!Quiet) {
            fprintf(stdout, "\tFAILED re-search failed: %d (%s)\n",
                    ntohl(ret), afp_error(ret));
        }

        test_failed();
        goto fin;
    }

    if (!Quiet) {
        fprintf(stdout,
                "\t  ✓ Catalog search handled concurrent renames without crashes or corruption\n");
    }

fin:

    /* Cleanup: Delete all files (both original and renamed) */
    if (dir) {
        for (i = 1; i <= 20; i++) {
            snprintf(base_name, sizeof(base_name), "search_file_%02d.pdf", i);
            FPDelete(Conn, vol, dir, base_name);  /* Best effort cleanup */
            snprintf(renamed_name, sizeof(renamed_name), "renamed_search_%02d.pdf", i);
            FPDelete(Conn, vol, dir, renamed_name);  /* Best effort cleanup */
        }

        FPDelete(Conn, vol, DIRDID_ROOT, dir_name);
    }

    if (vol2) {
        FPCloseVol(Conn2, vol2);
    }

test_exit:
    exit_test("FPCatSearchExt:test529: Catalog search with concurrent renames");
}

/* ------------------------- */
STATIC void test530()
{
    DSI *dsi;
    ENTER_TEST

    if (Conn->afp_version < 30) {
        test_skipped(T_AFP3);
        goto test_exit;
    }

    dsi = &Conn->dsi;
    catsearch_ext_send_raw(0x42, 0, 1 << FILPBIT_ATTR, 0xffff, NULL, 0, 0,
                           NULL, 0);

    if (dsi->header.dsi_code != htonl(AFPERR_PARAM)) {
        test_failed();
    }

test_exit:
    exit_test("FPCatSearchExt:test530: malformed search-spec length");
}

/* ------------------------- */
STATIC void test549()
{
    DSI *dsi;
    ENTER_TEST

    if (Conn->afp_version < 30) {
        test_skipped(T_AFP3);
        goto test_exit;
    }

    dsi = &Conn->dsi;
    catsearch_ext_send_raw(0x42, 0, 1 << FILPBIT_ATTR, 0, NULL, 0, 0, NULL,
                           0);

    if (dsi->header.dsi_code != htonl(AFPERR_PARAM)) {
        test_failed();
    }

test_exit:
    exit_test("FPCatSearchExt:test549: truncated search-spec payload");
}

/* ------------------------- */
STATIC void test550()
{
    uint16_t name_offset;
    unsigned char spec1[2];
    unsigned char spec2[3];
    DSI *dsi;
    ENTER_TEST

    if (Conn->afp_version < 30) {
        test_skipped(T_AFP3);
        goto test_exit;
    }

    dsi = &Conn->dsi;
    memset(spec1, 0, sizeof(spec1));
    memset(spec2, 0, sizeof(spec2));
    name_offset = htons(2);
    memcpy(spec1, &name_offset, sizeof(name_offset));
    memcpy(spec2, &name_offset, sizeof(name_offset));
    catsearch_ext_send_raw(0x42, 0, 1 << FILPBIT_LNAME, sizeof(spec1), spec1,
                           sizeof(spec1), sizeof(spec2), spec2, sizeof(spec2));

    if (dsi->header.dsi_code != htonl(AFPERR_PARAM)) {
        test_failed();
    }

test_exit:
    exit_test("FPCatSearchExt:test550: bad long-name search-spec offset");
}

/* ------------------------- */
STATIC void test552()
{
    uint16_t name_offset;
    unsigned char spec1[6];
    unsigned char spec2[6];
    DSI *dsi;
    ENTER_TEST

    if (Conn->afp_version < 30) {
        test_skipped(T_AFP3);
        goto test_exit;
    }

    dsi = &Conn->dsi;
    memset(spec1, 0, sizeof(spec1));
    memset(spec2, 0, sizeof(spec2));
    name_offset = htons(7);
    memcpy(spec1, &name_offset, sizeof(name_offset));
    catsearch_ext_send_raw(0x42, 0, 1 << FILPBIT_PDINFO, sizeof(spec1), spec1,
                           sizeof(spec1), sizeof(spec2), spec2, sizeof(spec2));

    if (dsi->header.dsi_code != htonl(AFPERR_PARAM)) {
        test_failed();
    }

test_exit:
    exit_test("FPCatSearchExt:test552: bad UTF8-name search-spec offset");
}

/* ------------------------- */
STATIC void test553()
{
    uint16_t name_len;
    unsigned char spec1[7];
    unsigned char spec2[6];
    DSI *dsi;
    ENTER_TEST

    if (Conn->afp_version < 30) {
        test_skipped(T_AFP3);
        goto test_exit;
    }

    dsi = &Conn->dsi;
    memset(spec1, 0, sizeof(spec1));
    memset(spec2, 0, sizeof(spec2));
    name_len = htons(2);
    memcpy(spec1 + 4, &name_len, sizeof(name_len));
    catsearch_ext_send_raw(0x42, 0, 1 << FILPBIT_PDINFO, sizeof(spec1), spec1,
                           sizeof(spec1), sizeof(spec2), spec2, sizeof(spec2));

    if (dsi->header.dsi_code != htonl(AFPERR_PARAM)) {
        test_failed();
    }

test_exit:
    exit_test("FPCatSearchExt:test553: bad UTF8-name search-spec length");
}

/* ------------------------- */
STATIC void test554()
{
    DSI *dsi;
    ENTER_TEST

    if (Conn->afp_version < 30) {
        test_skipped(T_AFP3);
        goto test_exit;
    }

    dsi = &Conn->dsi;
    catsearch_ext_send_raw(1 << FILPBIT_ATTR, 1 << DIRPBIT_ATTR,
                           1 << DIRPBIT_OFFCNT, 0, NULL, 0, 0, NULL, 0);

    if (dsi->header.dsi_code != htonl(AFPERR_BITMAP)) {
        test_failed();
    }

test_exit:
    exit_test("FPCatSearchExt:test554: conflicting search-result bitmaps");
}

/* ------------------------- */
STATIC void test555()
{
    uint16_t offset;
    uint16_t name_len;
    unsigned char spec1[23];
    DSI *dsi;
    ENTER_TEST

    if (Conn->afp_version < 30) {
        test_skipped(T_AFP3);
        goto test_exit;
    }

    dsi = &Conn->dsi;
    memset(spec1, 0, sizeof(spec1));
    offset = htons(8);
    memcpy(spec1, &offset, sizeof(offset));
    offset = htons(13);
    memcpy(spec1 + 2, &offset, sizeof(offset));
    spec1[8] = 4;
    memcpy(spec1 + 9, "Data", 4);
    name_len = htons(4);
    memcpy(spec1 + 17, &name_len, sizeof(name_len));
    memcpy(spec1 + 19, "Data", 4);
    catsearch_ext_send_raw(0x42, 0, (1 << FILPBIT_LNAME) | (1 << FILPBIT_PDINFO),
                           sizeof(spec1), spec1, sizeof(spec1), sizeof(spec1),
                           spec1, sizeof(spec1));

    if (dsi->header.dsi_code != 0
            && dsi->header.dsi_code != htonl(AFPERR_EOF)) {
        test_failed();
    }

test_exit:
    exit_test("FPCatSearchExt:test555: long-name and UTF8-name search-spec layout");
}

/* ----------- */
void FPCatSearchExt_test()
{
    ENTER_TESTSET
    test227();
    test529();
    test530();
    test549();
    test550();
    test552();
    test553();
    test554();
    test555();
}
