#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest.h"

// Redefine to public for having full access to the data when testing
#define private public
#define protected public

#include "netlib.hpp"

using namespace netlib;

TEST_CASE("Test IpAddr::V4") {

    auto strAddr = "192.168.13.37";

    auto ipv4 = IpAddr::V4(strAddr);

    CHECK( ipv4.type == IpAddr::Type::V4 );
    CHECK( ipv4.isIpv4() == true );
    CHECK( ipv4.isIpv6() == false );

    CHECK( ipv4.str_addr == strAddr );

    CHECK( ipv4.raw_addr.v4.s_addr == 0x250DA8C0);

}

TEST_CASE("Test IpAddr automatic version") {
    
    auto strAddrV4 = "192.168.13.37";

    auto ipv4 = IpAddr(strAddrV4);

    CHECK( ipv4.type == IpAddr::Type::V4 );
    CHECK( ipv4.isIpv4() == true );
    CHECK( ipv4.isIpv6() == false );

    CHECK( ipv4.str_addr == strAddrV4 );

    CHECK( ipv4.raw_addr.v4.s_addr == 0x250DA8C0);

    

    auto strAddrV6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
    uint8_t addrV6bytes[16] = { 
        0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00,
        0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34
    };

    auto ipv6 = IpAddr(strAddrV6);

    CHECK( ipv6.type == IpAddr::Type::V6 );
    CHECK( ipv6.isIpv4() == false );
    CHECK( ipv6.isIpv6() == true );

    CHECK( ipv6.str_addr == strAddrV6 );

    bool raw_ip_equal = true;
    for (auto i = 0; i < 16; i++)
    {
        if (((uint8_t*)&ipv6.raw_addr.v6)[i] != addrV6bytes[i] )
            raw_ip_equal = false;
    }

    CHECK( raw_ip_equal == true );

}

TEST_CASE("Test IpAddr parsing error") {

    CHECK_THROWS( IpAddr("") );

    CHECK_THROWS( IpAddr("0.0.0.0.0") );
    CHECK_THROWS( IpAddr("256.0.0.0") );
    CHECK_THROWS( IpAddr("-5.0.0.0") );
    CHECK_THROWS( IpAddr("a.0.0.0") );

    CHECK_THROWS( IpAddr("g::0") );
    CHECK_THROWS( IpAddr("10000::0") );
    CHECK_THROWS( IpAddr("1:1:1:1:1:1:1:1:1") );

    CHECK_THROWS( IpAddr::V4("::1") );

    CHECK_THROWS( IpAddr::V4("") );

    CHECK_THROWS( IpAddr::V6("127.0.0.1") );

    CHECK_THROWS( IpAddr::V6("") );

}

TEST_CASE("Test SockAddr from ip:port string") {

    SockAddr sa4("192.168.13.37:1337");

    CHECK( sa4.address.type == IpAddr::Type::V4 );
    CHECK( sa4.getIpAddressString() == "192.168.13.37" );
    CHECK( sa4.port == 1337 );

    SockAddr sa6("[::1]:1337");

    CHECK( sa6.address.type == IpAddr::Type::V6 );
    CHECK( sa6.getIpAddressString() == "::1" );
    CHECK( sa6.port == 1337 );

    // Invalid port
    CHECK_THROWS( SockAddr("127.0.0.1:80808") );
    CHECK_THROWS( SockAddr("127.0.0.1:abc") );
    CHECK_THROWS( SockAddr("127.0.0.1:") );
    CHECK_THROWS( SockAddr("127.0.0.1") );

    CHECK_THROWS( SockAddr("[::1]:80808") );
    CHECK_THROWS( SockAddr("[::1]:abc") );
    CHECK_THROWS( SockAddr("[::1]:") );
    CHECK_THROWS( SockAddr("[::1]") );

    CHECK_THROWS( SockAddr("::1]:8080") );
    CHECK_THROWS( SockAddr("[::1:8080") );
    CHECK_THROWS( SockAddr("[::1.]:8080") );
    CHECK_THROWS( SockAddr("[127.0.0.1]:8080") );

    CHECK_THROWS( SockAddr("") );

}

TEST_CASE("Test SockAddr raw_sockaddr") {
    std::string ip_addr_str = "192.168.13.37";
    uint16_t port = 1337;

    IpAddr ip(ip_addr_str);

    SockAddr sa(ip, port);

    auto raw_addr4 = (sockaddr_in*)&sa.raw_sockaddr;


    CHECK( sa.getIpAddressString() == ip_addr_str );

    CHECK( sa.port == port );

    CHECK( sa.raw_sockaddr.generic.sa_family == AF_INET );

    CHECK( raw_addr4->sin_port == htons(port) );

    CHECK( raw_addr4->sin_addr.s_addr == ip.raw_addr.v4.s_addr );


    ip.type = IpAddr::Type::Undef;

    CHECK_THROWS( SockAddr(ip, port) );

}


TEST_CASE("Test SockAddr from raw_sockaddr") {
    std::string ip_str = "192.168.13.37";
    uint16_t port = 1337;


    sockaddr_in raw;
    memset(&raw, 0, sizeof(raw));

    raw.sin_family = AF_INET;
    raw.sin_port = htons(1337);
    raw.sin_addr.s_addr = 0x250DA8C0; // 192.168.13.37 as int

    SockAddr sa((sockaddr*)&raw, IpAddr::Type::V4);

    CHECK( sa.address.type == IpAddr::Type::V4 );

    CHECK( sa.getIpAddressString() == ip_str );

    CHECK( sa.getPort() == port );

    CHECK_THROWS( SockAddr((sockaddr*)&raw, IpAddr::Type::Undef) );


    std::string ip_str6 = "2001:1db8:85a3::8a2e:1370:7334";
    uint8_t ip6_bytes[16] = { // The ipv6 address from above as bytes
        0x20, 0x01, 0x1d, 0xb8, 0x85, 0xa3, 0x00, 0x00,
        0x00, 0x00, 0x8a, 0x2e, 0x13, 0x70, 0x73, 0x34
    };
    
    sockaddr_in6 raw6;
    memset(&raw6, 0, sizeof(raw6));

    raw6.sin6_family = AF_INET6;
    raw6.sin6_port = htons(1337);
    memcpy(&raw6.sin6_addr, ip6_bytes, 16);

    SockAddr sa6((sockaddr*) &raw6, IpAddr::Type::V6);

    CHECK( sa6.address.type == IpAddr::Type::V6 );

    CHECK( sa6.getIpAddressString() == ip_str6 );

    CHECK( sa6.getPort() == port );


}


// This test requires internet and a working dns config
TEST_CASE("Test Resolver") {

    IpAddr ip4 = Resolver::resolveHostnameIpv4("one.one.one.one");

    CHECK( ip4.type == IpAddr::Type::V4 );

    // The hostname can resolve to either 1.1.1.1 or the fallback 1.0.0.1
    CHECK((
        ip4.getAddressString() == "1.1.1.1" 
        || 
        ip4.getAddressString() == "1.0.0.1"
    ));


    IpAddr ip6 = Resolver::resolveHostnameIpv6("one.one.one.one");

    CHECK( ip6.type == IpAddr::Type::V6 );

    // The hostname can resolve to either 2606:4700:4700::1111 or the fallback 
    // 2606:4700:4700::1001
    CHECK((
        ip6.getAddressString() == "2606:4700:4700::1111" 
        || 
        ip6.getAddressString() == "2606:4700:4700::1001"
    ));


    std::vector<IpAddr> ips = Resolver::resolveHostnameAll("one.one.one.one");

    // Search for 1.1.1.1
    bool found1 = std::find_if(
        ips.begin(), ips.end(), 
        [](const IpAddr &first) {
            return first.str_addr == "1.1.1.1";
        }
    ) != ips.end();

    CHECK( found1 == true );

    // Search for 1.0.0.1
    bool found2 = std::find_if(
        ips.begin(), ips.end(), 
        [](const IpAddr &first) {
            return first.str_addr == "1.0.0.1";
        }
    ) != ips.end();

    CHECK( found2 == true );

    // Search for 2606:4700:4700::1111
    bool found3 = std::find_if(
        ips.begin(), ips.end(), 
        [](const IpAddr &first) {
            return first.str_addr == "2606:4700:4700::1111";
        }
    ) != ips.end();

    CHECK( found3 == true );

    // Search for 2606:4700:4700::1001
    bool found4 = std::find_if(
        ips.begin(), ips.end(), 
        [](const IpAddr &first) {
            return first.str_addr == "2606:4700:4700::1001";
        }
    ) != ips.end();

    CHECK( found4 == true );

}