#include "maxminddb_test_helper.h"

/* These globals are gross but it's the easiest way to mix calling
 * for_all_modes() and for_all_record_sizes() */
static int Current_Mode;
static const char *Current_Mode_Description;

void test_one_result(MMDB_s *mmdb, MMDB_lookup_result_s result,
                     const char *ip, const char *expect,
                     const char *function, const char *filename,
                     const char *mode_desc)
{
    int is_ok = ok(result.found_entry,
                   "got a result for an IP in the database - %s - %s - %s - %s",
                   function, ip, filename, mode_desc);

    if (!is_ok) {
        return;
    }

    MMDB_entry_data_s data =
        data_ok(&result, MMDB_DATA_TYPE_UTF8_STRING, "result{ip}", "ip", NULL);

    char *string = strndup(data.utf8_string, data.data_size);

    char *real_expect;
    if (mmdb->metadata.ip_version == 4 || strncmp(expect, "::", 2) == 0) {
        real_expect = strndup(expect, strlen(expect));
    } else {
        // When looking up IPv4 addresses in a mixed DB the result will be
        // something like "::1.2.3.4", not just "1.2.3.4".
        int maxlen = strlen(expect) + 3;
        real_expect = malloc(maxlen);
        snprintf(real_expect, maxlen, "::%s", expect);
    }

    is(string, real_expect,
       "found expected result for ip key - %s - %s - %s - %s", function, ip,
       filename, mode_desc);

    free(real_expect);
    free(string);
}

void test_one_ip(MMDB_s *mmdb, const char *ip, const char *expect,
                 const char *filename, const char *mode_desc)
{
    MMDB_lookup_result_s result =
        lookup_string_ok(mmdb, ip, filename, mode_desc);

    test_one_result(mmdb, result, ip, expect, "MMDB_lookup_string", filename,
                    mode_desc);

    result = lookup_sockaddr_ok(mmdb, ip, filename, mode_desc);
    test_one_result(mmdb, result, ip, expect, "MMDB_lookup_addrinfo", filename,
                    mode_desc);
}

void run_ipX_tests(const char *filename, const char **missing_ips,
                   int missing_ips_length, const char *pairs[][2],
                   int pairs_rows)
{
    const char *path = test_database_path(filename);
    int mode = Current_Mode;
    const char *mode_desc = Current_Mode_Description;

    MMDB_s *mmdb = open_ok(path, mode, mode_desc);
    free((void *)path);

    char desc_suffix[500];
    snprintf(desc_suffix, 500, "%s - %s", filename, mode_desc);

    for (int i = 0; i < missing_ips_length; i++) {
        const char *ip = missing_ips[i];

        MMDB_lookup_result_s result =
            lookup_string_ok(mmdb, ip, filename, mode_desc);

        ok(
            !result.found_entry,
            "no result entry struct returned for IP address not in the database (string lookup) - %s - %s - %s",
            ip, filename, mode_desc);

        result = lookup_sockaddr_ok(mmdb, ip, filename, mode_desc);

        ok(
            !result.found_entry,
            "no result entry struct returned for IP address not in the database (ipv4 lookup) - %s - %s - %s",
            ip, filename, mode_desc);
    }

    for (int i = 0; i < pairs_rows; i += 1) {
        const char *ip_to_lookup = pairs[i][0];
        const char *expect = pairs[i][1];

        test_one_ip(mmdb, ip_to_lookup, expect, filename, mode_desc);
    }

    MMDB_close(mmdb);
    free(mmdb);
}

void run_ipv4_tests(int UNUSED(
                        record_size), const char *filename, const char *UNUSED(
                        ignored))
{
    const char *pairs[9][2] = {
        { "1.1.1.1",  "1.1.1.1"  },
        { "1.1.1.2",  "1.1.1.2"  },
        { "1.1.1.3",  "1.1.1.2"  },
        { "1.1.1.7",  "1.1.1.4"  },
        { "1.1.1.9",  "1.1.1.8"  },
        { "1.1.1.15", "1.1.1.8"  },
        { "1.1.1.17", "1.1.1.16" },
        { "1.1.1.31", "1.1.1.16" },
        { "1.1.1.32", "1.1.1.32" },
    };

    const char *missing[1] = { "2.3.4.5" };
    run_ipX_tests(filename, missing, 1, pairs, 9);
}

void run_ipv6_tests(int UNUSED(
                        record_size), const char *filename, const char *UNUSED(
                        ignored))
{
    const char *pairs[9][2] = {
        { "::1:ffff:ffff", "::1:ffff:ffff" },
        { "::2:0:0",       "::2:0:0"       },
        { "::2:0:1a",      "::2:0:0"       },
        { "::2:0:40",      "::2:0:40"      },
        { "::2:0:4f",      "::2:0:40"      },
        { "::2:0:50",      "::2:0:50"      },
        { "::2:0:52",      "::2:0:50"      },
        { "::2:0:58",      "::2:0:58"      },
        { "::2:0:59",      "::2:0:58"      },
    };

    const char *missing[2] = { "2.3.4.5", "::abcd" };
    run_ipX_tests(filename, missing, 2, pairs, 9);
}

void all_record_sizes(int mode, const char *description)
{
    const char *ipv4_filename_fmts[] = {
        "MaxMind-DB-test-ipv4-%i.mmdb",
        "MaxMind-DB-test-mixed-%i.mmdb"
    };

    Current_Mode = mode;
    Current_Mode_Description = description;

    for (int i = 0; i < 2; i++) {
        for_all_record_sizes(ipv4_filename_fmts[i], &run_ipv4_tests);
    }

    const char *ipv6_filename_fmts[] = {
        "MaxMind-DB-test-ipv6-%i.mmdb",
        "MaxMind-DB-test-mixed-%i.mmdb"
    };

    for (int i = 0; i < 2; i++) {
        for_all_record_sizes(ipv6_filename_fmts[i], &run_ipv6_tests);
    }
}

int main(void)
{
    plan(NO_PLAN);
    for_all_modes(&all_record_sizes);
    done_testing();
}
