#include "ClientArgs.hpp"

#include <charconv>
#include <algorithm>
#include <cctype>

namespace tw::app {

namespace {

std::string_view trim(std::string_view str) {
    // Skip leading whitespace
    size_t start = 0;
    while(start < str.length() && std::isspace(static_cast<unsigned char>(str[start]))) {
        ++start;
    }

    // Skip trailing whitespace
    size_t end = str.length();
    while(end > start && std::isspace(static_cast<unsigned char>(str[end - 1]))) {
        --end;
    }

    return str.substr(start, end - start);
}

tl::expected<int, std::string> parse_port(std::string_view port_str) {
    if(port_str.empty()) {
        return 8080; // Default port
    }

    int port = 0;
    const char* end = port_str.data() + port_str.length();
    auto result = std::from_chars(port_str.data(), end, port);

    // from_chars stops at the first character it cannot use, so a partially
    // numeric port like "80x" would otherwise be accepted as 80.
    if(result.ec != std::errc() || result.ptr != end) {
        return tl::make_unexpected("port must be numeric");
    }

    if(port < 1 || port > 65535) {
        return tl::make_unexpected("port must be between 1 and 65535");
    }

    return port;
}

} // anonymous namespace

tl::expected<net::Address, std::string> parse_address(std::string_view text) {
    // Trim whitespace
    text = trim(text);

    if(text.empty()) {
        return tl::make_unexpected("address cannot be empty");
    }

    std::string_view host;
    std::string_view port_str;

    // An IPv6 literal carries colons of its own, so the brackets it is written
    // in are what says where the host ends. This is the form to_string() emits.
    if(text.front() == '[') {
        size_t closing = text.find(']');
        if(closing == std::string_view::npos) {
            return tl::make_unexpected("address is missing a closing bracket");
        }

        host = text.substr(1, closing - 1);
        port_str = text.substr(closing + 1);

        if(!port_str.empty()) {
            if(port_str.front() != ':') {
                return tl::make_unexpected("expected a port after the closing bracket");
            }
            port_str.remove_prefix(1);
        }
    } else if(size_t colon_pos = text.rfind(':'); colon_pos == std::string_view::npos) {
        bool all_digits = !text.empty() && std::all_of(text.begin(), text.end(),
            [](unsigned char c) { return std::isdigit(c); });

        if(all_digits) {
            host = "127.0.0.1";
            port_str = text;
        } else {
            host = text;
            port_str = "";
        }
    } else {
        host = text.substr(0, colon_pos);
        port_str = text.substr(colon_pos + 1);
    }

    // Validate host
    if(host.empty()) {
        return tl::make_unexpected("host cannot be empty");
    }

    // Parse port
    auto port_result = parse_port(port_str);
    if(!port_result) {
        return tl::make_unexpected(port_result.error());
    }

    int port = port_result.value();

    auto address_r = net::Address::resolve(std::string(host), port);
    if(!address_r) {
        return tl::make_unexpected(address_r.error().message());
    }

    return address_r.value();
}

std::optional<std::string> server_arg(int argc, char** argv) {
    for(int i = 1; i < argc - 1; ++i) {
        if(std::string_view(argv[i]) == "--server") {
            return std::string(argv[i + 1]);
        }
    }
    return std::nullopt;
}

} // namespace tw::app
