#include "networking.h"
#include <sstream>
#include <algorithm>
#include <stdexcept>
#include <fstream>
#include <cstring>
#include <regex>
#include <iomanip>

// Platform-specific includes
#ifdef _WIN32
    #include <winsock2.h>
    #include <ws2tcpip.h>
    #pragma comment(lib, "ws2_32.lib")
    typedef SOCKET socket_t;
    #define CLOSE_SOCKET closesocket
    #define SOCKET_ERROR_CODE WSAGetLastError()
#else
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <netdb.h>
    typedef int socket_t;
    #define INVALID_SOCKET -1
    #define SOCKET_ERROR -1
    #define CLOSE_SOCKET ::close
    #define SOCKET_ERROR_CODE errno
#endif

namespace includecpp {
namespace inbuilds {

// ===== INTERNAL HELPERS =====

namespace {
    class SocketInitializer {
    public:
        SocketInitializer() {
            #ifdef _WIN32
            WSADATA wsa_data;
            WSAStartup(MAKEWORD(2, 2), &wsa_data);
            #endif
        }

        ~SocketInitializer() {
            #ifdef _WIN32
            WSACleanup();
            #endif
        }
    };

    static SocketInitializer socket_init;

    std::string get_socket_error() {
        #ifdef _WIN32
        int error_code = WSAGetLastError();
        char* msg = nullptr;
        FormatMessageA(
            FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
            nullptr, error_code,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPSTR)&msg, 0, nullptr
        );
        std::string error_msg = msg ? msg : "Unknown error";
        LocalFree(msg);
        return error_msg;
        #else
        return std::string(strerror(errno));
        #endif
    }

    std::pair<std::string, std::string> parse_url(const std::string& url) {
        std::regex url_regex(R"(^(https?)://([^:/]+)(?::(\d+))?(/.*)?)");
        std::smatch match;

        if (!std::regex_match(url, match, url_regex)) {
            throw std::runtime_error("Invalid URL format");
        }

        std::string protocol = match[1];
        std::string host = match[2];
        std::string port_str = match[3];
        std::string path = match[4];

        int port = port_str.empty() ? (protocol == "https" ? 443 : 80) : std::stoi(port_str);

        if (path.empty()) path = "/";

        return {host, path};
    }

    HttpResponse create_error_response(const std::string& error) {
        HttpResponse response;
        response.status_code = 0;
        response.success = false;
        response.error = error;
        return response;
    }
}

// ===== HTTP CLASS =====

HttpResponse Http::get(const std::string& url) {
    HttpRequest req;
    req.method = "GET";
    req.url = url;
    return request(req);
}

HttpResponse Http::post(const std::string& url, const std::string& body) {
    HttpRequest req;
    req.method = "POST";
    req.url = url;
    req.body = body;
    return request(req);
}

HttpResponse Http::post_json(const std::string& url, const std::string& json) {
    HttpRequest req;
    req.method = "POST";
    req.url = url;
    req.body = json;
    req.headers["Content-Type"] = "application/json";
    return request(req);
}

HttpResponse Http::post_form(const std::string& url, const std::map<std::string, std::string>& form_data) {
    HttpRequest req;
    req.method = "POST";
    req.url = url;
    req.body = build_query_string(form_data);
    req.headers["Content-Type"] = "application/x-www-form-urlencoded";
    return request(req);
}

HttpResponse Http::put(const std::string& url, const std::string& body) {
    HttpRequest req;
    req.method = "PUT";
    req.url = url;
    req.body = body;
    return request(req);
}

HttpResponse Http::patch(const std::string& url, const std::string& body) {
    HttpRequest req;
    req.method = "PATCH";
    req.url = url;
    req.body = body;
    return request(req);
}

HttpResponse Http::delete_request(const std::string& url) {
    HttpRequest req;
    req.method = "DELETE";
    req.url = url;
    return request(req);
}

HttpResponse Http::head(const std::string& url) {
    HttpRequest req;
    req.method = "HEAD";
    req.url = url;
    return request(req);
}

HttpResponse Http::options(const std::string& url) {
    HttpRequest req;
    req.method = "OPTIONS";
    req.url = url;
    return request(req);
}

HttpResponse Http::request(const HttpRequest& req) {
    try {
        auto [host, path] = parse_url(req.url);

        // Create socket
        socket_t sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (sock == INVALID_SOCKET) {
            return create_error_response("Failed to create socket: " + get_socket_error());
        }

        // Set timeout
        #ifdef _WIN32
        DWORD timeout = req.timeout_ms;
        setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
        setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout));
        #else
        struct timeval tv;
        tv.tv_sec = req.timeout_ms / 1000;
        tv.tv_usec = (req.timeout_ms % 1000) * 1000;
        setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
        setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
        #endif

        // Resolve hostname
        struct hostent* server = gethostbyname(host.c_str());
        if (server == nullptr) {
            CLOSE_SOCKET(sock);
            return create_error_response("Failed to resolve hostname: " + host);
        }

        // Connect
        struct sockaddr_in serv_addr;
        std::memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        std::memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length);

        // Determine port from URL
        int port = 80;
        if (req.url.find("https://") == 0) port = 443;
        serv_addr.sin_port = htons(port);

        if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
            CLOSE_SOCKET(sock);
            return create_error_response("Failed to connect: " + get_socket_error());
        }

        // Build HTTP request
        std::ostringstream request_stream;
        request_stream << req.method << " " << path << " HTTP/1.1\r\n";
        request_stream << "Host: " << host << "\r\n";
        request_stream << "User-Agent: IncludeCPP/1.0\r\n";
        request_stream << "Accept: */*\r\n";
        request_stream << "Connection: close\r\n";

        for (const auto& [key, value] : req.headers) {
            request_stream << key << ": " << value << "\r\n";
        }

        if (!req.body.empty()) {
            request_stream << "Content-Length: " << req.body.length() << "\r\n";
        }

        request_stream << "\r\n";

        if (!req.body.empty()) {
            request_stream << req.body;
        }

        std::string request_str = request_stream.str();

        // Send request
        if (send(sock, request_str.c_str(), request_str.length(), 0) < 0) {
            CLOSE_SOCKET(sock);
            return create_error_response("Failed to send request: " + get_socket_error());
        }

        // Receive response
        std::string response_data;
        char buffer[4096];
        int bytes_received;

        while ((bytes_received = recv(sock, buffer, sizeof(buffer) - 1, 0)) > 0) {
            buffer[bytes_received] = '\0';
            response_data += buffer;
        }

        CLOSE_SOCKET(sock);

        // Parse response
        HttpResponse response;
        response.success = true;

        size_t header_end = response_data.find("\r\n\r\n");
        if (header_end == std::string::npos) {
            return create_error_response("Invalid HTTP response");
        }

        std::string headers_str = response_data.substr(0, header_end);
        response.body = response_data.substr(header_end + 4);

        // Parse status line
        std::istringstream header_stream(headers_str);
        std::string status_line;
        std::getline(header_stream, status_line);

        std::istringstream status_stream(status_line);
        std::string http_version;
        status_stream >> http_version >> response.status_code;
        std::getline(status_stream, response.status_text);

        // Trim status text
        response.status_text.erase(0, response.status_text.find_first_not_of(" \t"));

        // Parse headers
        std::string header_line;
        while (std::getline(header_stream, header_line) && !header_line.empty()) {
            if (header_line.back() == '\r') {
                header_line.pop_back();
            }

            size_t colon_pos = header_line.find(':');
            if (colon_pos != std::string::npos) {
                std::string key = header_line.substr(0, colon_pos);
                std::string value = header_line.substr(colon_pos + 1);

                // Trim
                key.erase(0, key.find_first_not_of(" \t"));
                key.erase(key.find_last_not_of(" \t") + 1);
                value.erase(0, value.find_first_not_of(" \t"));
                value.erase(value.find_last_not_of(" \t") + 1);

                response.headers[key] = value;
            }
        }

        return response;

    } catch (const std::exception& e) {
        return create_error_response(std::string("Exception: ") + e.what());
    }
}

bool Http::download_file(const std::string& url, const std::string& filepath) {
    HttpResponse response = get(url);
    if (!response.success) return false;

    std::ofstream file(filepath, std::ios::binary);
    if (!file.is_open()) return false;

    file << response.body;
    return file.good();
}

bool Http::upload_file(const std::string& url, const std::string& filepath) {
    std::ifstream file(filepath, std::ios::binary);
    if (!file.is_open()) return false;

    std::ostringstream ss;
    ss << file.rdbuf();
    std::string content = ss.str();

    HttpResponse response = post(url, content);
    return response.success;
}

std::string Http::url_encode(const std::string& str) {
    std::ostringstream escaped;
    escaped.fill('0');
    escaped << std::hex;

    for (unsigned char c : str) {
        if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
            escaped << c;
        } else {
            escaped << '%' << std::setw(2) << static_cast<int>(c);
        }
    }

    return escaped.str();
}

std::string Http::url_decode(const std::string& str) {
    std::string result;
    for (size_t i = 0; i < str.length(); ++i) {
        if (str[i] == '%') {
            if (i + 2 < str.length()) {
                std::string hex = str.substr(i + 1, 2);
                char decoded = static_cast<char>(std::stoi(hex, nullptr, 16));
                result += decoded;
                i += 2;
            }
        } else if (str[i] == '+') {
            result += ' ';
        } else {
            result += str[i];
        }
    }
    return result;
}

std::map<std::string, std::string> Http::parse_query_string(const std::string& query) {
    std::map<std::string, std::string> params;
    std::istringstream stream(query);
    std::string pair;

    while (std::getline(stream, pair, '&')) {
        size_t eq_pos = pair.find('=');
        if (eq_pos != std::string::npos) {
            std::string key = url_decode(pair.substr(0, eq_pos));
            std::string value = url_decode(pair.substr(eq_pos + 1));
            params[key] = value;
        }
    }

    return params;
}

std::string Http::build_query_string(const std::map<std::string, std::string>& params) {
    std::ostringstream ss;
    bool first = true;

    for (const auto& [key, value] : params) {
        if (!first) ss << '&';
        ss << url_encode(key) << '=' << url_encode(value);
        first = false;
    }

    return ss.str();
}

// ===== URL CLASS =====

std::string Url::get_protocol(const std::string& url) {
    size_t pos = url.find("://");
    if (pos == std::string::npos) return "";
    return url.substr(0, pos);
}

std::string Url::get_hostname(const std::string& url) {
    size_t start = url.find("://");
    if (start == std::string::npos) start = 0;
    else start += 3;

    size_t end = url.find(':', start);
    if (end == std::string::npos) end = url.find('/', start);
    if (end == std::string::npos) end = url.length();

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

int Url::get_port(const std::string& url) {
    size_t start = url.find("://");
    if (start == std::string::npos) start = 0;
    else start += 3;

    size_t colon_pos = url.find(':', start);
    if (colon_pos == std::string::npos) {
        std::string protocol = get_protocol(url);
        return (protocol == "https") ? 443 : 80;
    }

    size_t end = url.find('/', colon_pos);
    if (end == std::string::npos) end = url.length();

    std::string port_str = url.substr(colon_pos + 1, end - colon_pos - 1);
    return std::stoi(port_str);
}

std::string Url::get_path(const std::string& url) {
    size_t start = url.find("://");
    if (start == std::string::npos) start = 0;
    else start += 3;

    size_t slash_pos = url.find('/', start);
    if (slash_pos == std::string::npos) return "/";

    size_t query_pos = url.find('?', slash_pos);
    size_t fragment_pos = url.find('#', slash_pos);

    size_t end = std::min(
        query_pos == std::string::npos ? url.length() : query_pos,
        fragment_pos == std::string::npos ? url.length() : fragment_pos
    );

    return url.substr(slash_pos, end - slash_pos);
}

std::string Url::get_query(const std::string& url) {
    size_t query_pos = url.find('?');
    if (query_pos == std::string::npos) return "";

    size_t fragment_pos = url.find('#', query_pos);
    size_t end = fragment_pos == std::string::npos ? url.length() : fragment_pos;

    return url.substr(query_pos + 1, end - query_pos - 1);
}

std::string Url::get_fragment(const std::string& url) {
    size_t fragment_pos = url.find('#');
    if (fragment_pos == std::string::npos) return "";

    return url.substr(fragment_pos + 1);
}

std::string Url::build(const std::string& protocol, const std::string& host, int port, const std::string& path) {
    std::ostringstream ss;
    ss << protocol << "://" << host;

    if ((protocol == "http" && port != 80) || (protocol == "https" && port != 443)) {
        ss << ":" << port;
    }

    ss << path;
    return ss.str();
}

std::string Url::join(const std::string& base, const std::string& path) {
    if (path.empty()) return base;
    if (is_absolute(path)) return path;

    std::string result = base;
    if (result.back() != '/' && path.front() != '/') {
        result += '/';
    } else if (result.back() == '/' && path.front() == '/') {
        result.pop_back();
    }

    result += path;
    return result;
}

bool Url::is_valid(const std::string& url) {
    std::regex url_regex(R"(^https?://[^\s/$.?#].[^\s]*$)", std::regex::icase);
    return std::regex_match(url, url_regex);
}

bool Url::is_absolute(const std::string& url) {
    return url.find("://") != std::string::npos;
}

bool Url::is_relative(const std::string& url) {
    return !is_absolute(url);
}

// ===== TCP CLIENT =====

TcpClient::TcpClient() : socket_fd(nullptr), connected(false) {}

TcpClient::~TcpClient() {
    disconnect();
}

bool TcpClient::connect(const std::string& host, int port) {
    socket_t sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock == INVALID_SOCKET) {
        last_error = "Failed to create socket: " + get_socket_error();
        return false;
    }

    struct hostent* server = gethostbyname(host.c_str());
    if (server == nullptr) {
        last_error = "Failed to resolve hostname: " + host;
        CLOSE_SOCKET(sock);
        return false;
    }

    struct sockaddr_in serv_addr;
    std::memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    std::memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length);
    serv_addr.sin_port = htons(port);

    if (::connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
        last_error = "Failed to connect: " + get_socket_error();
        CLOSE_SOCKET(sock);
        return false;
    }

    socket_fd = new socket_t(sock);
    connected = true;
    return true;
}

bool TcpClient::disconnect() {
    if (socket_fd) {
        CLOSE_SOCKET(*static_cast<socket_t*>(socket_fd));
        delete static_cast<socket_t*>(socket_fd);
        socket_fd = nullptr;
        connected = false;
    }
    return true;
}

bool TcpClient::is_connected() const {
    return connected;
}

int TcpClient::send(const std::string& data) {
    if (!connected || !socket_fd) {
        last_error = "Not connected";
        return -1;
    }

    int result = ::send(*static_cast<socket_t*>(socket_fd), data.c_str(), data.length(), 0);
    if (result < 0) {
        last_error = "Send failed: " + get_socket_error();
    }

    return result;
}

std::string TcpClient::receive(int max_bytes) {
    if (!connected || !socket_fd) {
        last_error = "Not connected";
        return "";
    }

    std::vector<char> buffer(max_bytes);
    int bytes_received = recv(*static_cast<socket_t*>(socket_fd), buffer.data(), buffer.size(), 0);

    if (bytes_received < 0) {
        last_error = "Receive failed: " + get_socket_error();
        return "";
    }

    return std::string(buffer.data(), bytes_received);
}

std::string TcpClient::receive_line() {
    std::string line;
    char c;

    while (true) {
        int result = recv(*static_cast<socket_t*>(socket_fd), &c, 1, 0);
        if (result <= 0) break;

        if (c == '\n') break;
        if (c != '\r') line += c;
    }

    return line;
}

bool TcpClient::set_timeout(int milliseconds) {
    if (!socket_fd) return false;

    #ifdef _WIN32
    DWORD timeout = milliseconds;
    setsockopt(*static_cast<socket_t*>(socket_fd), SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
    setsockopt(*static_cast<socket_t*>(socket_fd), SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout));
    #else
    struct timeval tv;
    tv.tv_sec = milliseconds / 1000;
    tv.tv_usec = (milliseconds % 1000) * 1000;
    setsockopt(*static_cast<socket_t*>(socket_fd), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
    setsockopt(*static_cast<socket_t*>(socket_fd), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
    #endif

    return true;
}

std::string TcpClient::get_error() const {
    return last_error;
}

// ===== UDP SOCKET =====

UdpSocket::UdpSocket() : socket_fd(nullptr), is_bound(false) {}

UdpSocket::~UdpSocket() {
    close();
}

bool UdpSocket::bind(const std::string& host, int port) {
    socket_t sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sock == INVALID_SOCKET) {
        last_error = "Failed to create socket: " + get_socket_error();
        return false;
    }

    struct sockaddr_in addr;
    std::memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = host == "0.0.0.0" ? INADDR_ANY : inet_addr(host.c_str());
    addr.sin_port = htons(port);

    if (::bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        last_error = "Failed to bind: " + get_socket_error();
        CLOSE_SOCKET(sock);
        return false;
    }

    socket_fd = new socket_t(sock);
    is_bound = true;
    return true;
}

bool UdpSocket::close() {
    if (socket_fd) {
        CLOSE_SOCKET(*static_cast<socket_t*>(socket_fd));
        delete static_cast<socket_t*>(socket_fd);
        socket_fd = nullptr;
        is_bound = false;
    }
    return true;
}

int UdpSocket::send_to(const std::string& data, const std::string& dest_host, int dest_port) {
    if (!socket_fd) {
        // Create temporary socket if not bound
        socket_t sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        if (sock == INVALID_SOCKET) {
            last_error = "Failed to create socket: " + get_socket_error();
            return -1;
        }
        socket_fd = new socket_t(sock);
    }

    struct sockaddr_in dest_addr;
    std::memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_addr.s_addr = inet_addr(dest_host.c_str());
    dest_addr.sin_port = htons(dest_port);

    int result = sendto(*static_cast<socket_t*>(socket_fd), data.c_str(), data.length(), 0,
                       (struct sockaddr*)&dest_addr, sizeof(dest_addr));

    if (result < 0) {
        last_error = "Send failed: " + get_socket_error();
    }

    return result;
}

std::string UdpSocket::receive_from(std::string& from_host, int& from_port, int max_bytes) {
    if (!is_bound || !socket_fd) {
        last_error = "Socket not bound";
        return "";
    }

    std::vector<char> buffer(max_bytes);
    struct sockaddr_in from_addr;
    socklen_t from_len = sizeof(from_addr);

    int bytes_received = recvfrom(*static_cast<socket_t*>(socket_fd), buffer.data(), buffer.size(), 0,
                                 (struct sockaddr*)&from_addr, &from_len);

    if (bytes_received < 0) {
        last_error = "Receive failed: " + get_socket_error();
        return "";
    }

    from_host = inet_ntoa(from_addr.sin_addr);
    from_port = ntohs(from_addr.sin_port);

    return std::string(buffer.data(), bytes_received);
}

bool UdpSocket::set_timeout(int milliseconds) {
    if (!socket_fd) return false;

    #ifdef _WIN32
    DWORD timeout = milliseconds;
    setsockopt(*static_cast<socket_t*>(socket_fd), SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
    #else
    struct timeval tv;
    tv.tv_sec = milliseconds / 1000;
    tv.tv_usec = (milliseconds % 1000) * 1000;
    setsockopt(*static_cast<socket_t*>(socket_fd), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
    #endif

    return true;
}

std::string UdpSocket::get_error() const {
    return last_error;
}

// ===== FREE FUNCTIONS =====

HttpResponse http_get(const std::string& url) {
    return Http::get(url);
}

HttpResponse http_post(const std::string& url, const std::string& body) {
    return Http::post(url, body);
}

std::string fetch(const std::string& url) {
    HttpResponse response = Http::get(url);
    return response.success ? response.body : "";
}

} // namespace inbuilds
} // namespace includecpp
