#include "test.h"
#include <string>

#define TEST(name) TEST_CASE("parse-stringify: " name, "[parse-stringify]")

static int flags;

URI test (string url, string scheme, string uinfo, string host, uint16_t port = 0, string path = "", string qstr = "", string fragment = "", string checkstr = "") {
    std::string testname = "test url " + url;
    if (flags) testname += " (flags=" + panda::to_string(flags) + ")";
    if (!checkstr) checkstr = url;

    URI uri(url, flags);

    SECTION(testname) {
        CHECK(uri.scheme() == scheme);
        CHECK(uri.user_info() == uinfo);
        CHECK(uri.host() == host);
        CHECK(uri.explicit_port() == port);
        CHECK(uri.path() == path);
        CHECK(uri.query_string() == qstr);
        CHECK(uri.fragment() == fragment);
        CHECK(uri.to_string() == checkstr);
    }

    return uri;
}

void test_wrong (string url) {
    SECTION("test bad url " + std::string(url.c_str())) {
        CHECK(URI(url) == URI());
    }
}

TEST("empty") {
    test("", "", "", "");
}

TEST("scheme") {
    SECTION("scheme -> authority") {
        test("http://host", "http", "", "host");
        test("ws://host", "ws", "", "host");
    }
    SECTION("scheme -> path") {
        test("mailto:syber@crazypanda.ru", "mailto", "", "", 0, "syber@crazypanda.ru");
        test("a:b:c:d:e:f", "a", "", "", 0, "b:c:d:e:f");
        test("cp:/jopa", "cp", "", "", 0, "/jopa");
    }
    SECTION("scheme-relative") {
        test("//ya.ru", "", "", "ya.ru");
    }
}

TEST("user info") {
    test("http://user@ya.ru", "http", "user", "ya.ru");
    test("http://user:pass@ya.ru", "http", "user:pass", "ya.ru");
    test("http://user:@ya.ru", "http", "user:", "ya.ru");
    test_wrong("http://cool@user@ya.ru"); // invalid chars
}

TEST("host") {
    SECTION("reg name") {
        test("http://ya.ru", "http", "", "ya.ru");
    }
    SECTION("IPv4") {
        test("http://1.10.100.255", "http", "", "1.10.100.255");
        test("http://0.0.0.0", "http", "", "0.0.0.0");
    }
    SECTION("IPv6") {
        test("http://[aa:bb:cc:dd::ee:ff]", "http", "", "[aa:bb:cc:dd::ee:ff]");
        test("http://[aa:bb:cc:dd::]", "http", "", "[aa:bb:cc:dd::]");
        test("http://user@[::ee:ff]", "http", "user", "[::ee:ff]");
        test_wrong("http://[aa:bb:cc:dd:ee:ff]"); // wrong address
        test_wrong("http://[aa:bb:cc:dd::ee:ff"); // wrong address
        test_wrong("http://[aa:bb:cc:dd:ee:::ff]"); // wrong address
    }
}

TEST("port") {
    SECTION("explicit") {
        test("http://ya.ru", "http", "", "ya.ru", 0);
        test("abc://ya.ru:80", "abc", "", "ya.ru", 80);
        test("def://ya.ru:443", "def", "", "ya.ru", 443);
    }
    SECTION("implicit") {
        URI uri("http://ya.ru");
        CHECK(uri.port() == 80);
        uri = "http://ya.ru:81";
        CHECK(uri.port() == 81);
        uri = "https://ya.ru";
        CHECK(uri.port() == 443);
        uri = "https://ya.ru:444";
        CHECK(uri.port() == 444);
        uri = "hz://ya.ru";
        CHECK(uri.port() == 0);
    }
}

TEST("location") {
    SECTION("explicit") {
        URI uri("http://ya.ru");
        CHECK(uri.explicit_location() == "ya.ru");
        uri = "http://ya.ru:81";
        CHECK(uri.explicit_location() == "ya.ru:81");
    }
    SECTION("implicit") {
        URI uri("http://ya.ru");
        CHECK(uri.location() == "ya.ru:80");
        uri = "http://ya.ru:81";
        CHECK(uri.location() == "ya.ru:81");
        uri = "https://ya.ru";
        CHECK(uri.location() == "ya.ru:443");
        uri = "https://ya.ru:444";
        CHECK(uri.location() == "ya.ru:444");
        uri = "hz://ya.ru";
        CHECK(uri.location() == "ya.ru:0");
    }
}

TEST("path") {
    SECTION("absolute") {
        test("http://host", "http", "", "host", 0, "");
        test("http://host/", "http", "", "host", 0, "/");
        test("http://host/path", "http", "", "host", 0, "/path");
    }
    SECTION("scheme-relative") {
        test("//host", "", "", "host", 0, "");
        test("//host/", "", "", "host", 0, "/");
        test("//host/path", "", "", "host", 0, "/path");
    }
    SECTION("scheme->path") {
        test("about:", "about", "", "", 0, "");
        test("about:/", "about", "", "", 0, "/");
        test("about:/path", "about", "", "", 0, "/path");
        test("about:path", "about", "", "", 0, "path");
        test("about:path/", "about", "", "", 0, "path/");
    }
    SECTION("relative") {
        test("a", "", "", "", 0, "a");
        test("/", "", "", "", 0, "/");
        test("/abc", "", "", "", 0, "/abc");

        // according to RFC, "ya.ru" is not a host, it's a part of the path
        // to parse "ya.ru" as host (like browsers), we need to enable special mode ALLOW_SUFFIX_REFERENCE
        test("ya.ru/abc", "", "", "", 0, "ya.ru/abc");

        CHECK(URI("http://ya.ru").relative() == "/"); // not empty relative path
        CHECK(URI("http://ya.ru?p1=v1&p2=v2#myhash").relative() == "/?p1=v1&p2=v2#myhash");
    }
}

TEST("query string") {
    test("http://ya.ru?sukastring", "http", "", "ya.ru", 0, "", "sukastring");
    auto uri = test("http://ya.ru?suka%20string+nah", "http", "", "ya.ru", 0, "", "suka%20string+nah");
    CHECK(uri.raw_query() ==  "suka string nah");
}

TEST("fragment") {
    test("http://ya.ru#frag", "http", "", "ya.ru", 0, "", "", "frag");
    test("http://ya.ru#my%23frag", "http", "", "ya.ru", 0, "", "", "my%23frag");
    test("http://ya.ru?p1=v1#myhash", "http", "", "ya.ru", 0, "", "p1=v1", "myhash");
    test("https://jopa.com#a?b?c", "https", "", "jopa.com", 0, "", "", "a?b?c");
    test_wrong("http://ya.ru#my#frag"); // invalid chars in fragment;
}

TEST("leading authority euristics") {
    flags = URI::Flags::allow_suffix_reference;
    test("ya.ru:8080", "", "", "ya.ru", 8080, "", "", "", "//ya.ru:8080");
    test("ya.ru", "", "", "ya.ru", 0, "", "", "", "//ya.ru");
    test("ya.ru:", "ya.ru", "", "", 0);
    test("ya.ru:80a", "ya.ru", "", "", 0, "80a");
    test("ya.ru:8080/a/b", "", "", "ya.ru", 8080, "/a/b", "", "", "//ya.ru:8080/a/b");
    test("ya.ru/a/b", "", "", "ya.ru", 0, "/a/b", "", "", "//ya.ru/a/b");
    test("ya.ru:/a/b", "ya.ru", "", "", 0, "/a/b");
    test("ya.ru:80a/a/b", "ya.ru", "", "", 0, "80a/a/b");
    flags = 0;
}

TEST("allow extended chars") {
    URI uri("http://jopa.com?param={\"key\",\"val|hi\"}", URI::Flags::allow_extended_chars);
    CHECK(uri.query_string() == "param=%7B%22key%22%2C%22val%7Chi%22%7D");
    CHECK(uri.to_string() == "http://jopa.com?param=%7B%22key%22%2C%22val%7Chi%22%7D");
    CHECK(uri.query() == Query({{"param", "{\"key\",\"val|hi\"}"}}));
}

TEST("secure") {
    CHECK(URI("https://ya.ru").secure());
    CHECK(!URI("http://ya.ru").secure());
    CHECK(!URI("//ya.ru").secure());
    CHECK(!URI("ya.ru").secure());
}

TEST("misc") {
    test("mailto:syber@crazypanda.ru?a=b#dada", "mailto", "", "", 0, "syber@crazypanda.ru", "a=b", "dada");
    test("http://user@ya.ru:2345/my/path?p1=v1&p2=v2#myhash", "http", "user", "ya.ru", 2345, "/my/path", "p1=v1&p2=v2", "myhash");
    test("http://user:pass@ya.ru:2345/my/path?p1=v1&p2=v2#myhash", "http", "user:pass", "ya.ru", 2345, "/my/path", "p1=v1&p2=v2", "myhash");
    test("http://user:@ya.ru:2345/my/path?p1=v1&p2=v2#myhash", "http", "user:", "ya.ru", 2345, "/my/path", "p1=v1&p2=v2", "myhash");
    test("http://1.10.100.255:2345/my/path?p1=v1&p2=v2#myhash", "http", "", "1.10.100.255", 2345, "/my/path", "p1=v1&p2=v2", "myhash");
    test("http://hi@1.10.100.255:2345/my/path?p1=v1&p2=v2#myhash", "http", "hi", "1.10.100.255", 2345, "/my/path", "p1=v1&p2=v2", "myhash");
    test("http://[aa:bb:cc:dd::ee:ff]/my/path?p1=v1&p2=v2#myhash", "http", "", "[aa:bb:cc:dd::ee:ff]", 0, "/my/path", "p1=v1&p2=v2", "myhash");
    test("http://[aa:bb:cc:dd::]:2345/my/path?p1=v1&p2=v2#myhash", "http", "", "[aa:bb:cc:dd::]", 2345, "/my/path", "p1=v1&p2=v2", "myhash");
    test("http://user@[::ee:ff]:2345/my/path?p1=v1&p2=v2#myhash", "http", "user", "[::ee:ff]", 2345, "/my/path", "p1=v1&p2=v2", "myhash");
    test("//sss@ya.ru:2345/my/path?p1=v1&p2=v2#myhash", "", "sss", "ya.ru", 2345, "/my/path", "p1=v1&p2=v2", "myhash");
    test("//[aa:bb:cc:dd::ee:ff]/my/path?p1=v1&p2=v2#myhash", "", "", "[aa:bb:cc:dd::ee:ff]", 0, "/my/path", "p1=v1&p2=v2", "myhash");
}

TEST("bad") {
    CHECK(URI("http://api.odnokl\x5C\x00\x03\x06\x00\x00\x00\x00\x00\x00\x00\x23\xC3\xABlq\x1B\x00\x02") == URI()); // null byte in uri. should NOT core dump. Stop parsing url on null byte
    test_wrong("https://jopa.com:123/://asd/?:hello?://yo?u/#lalala://hello/?a=b&jopa=#privet");
}