#include "WebModule.h"
#include <grove/IIO.h>
#include <grove/JsonDataNode.h>
#include "../shared/http/HttpClient.hpp"
#include <chrono>
#include <regex>

namespace celuna {

WebModule::WebModule() {
    m_logger = spdlog::get("WebModule");
    if (!m_logger) {
        m_logger = spdlog::stdout_color_mt("WebModule");
    }
    m_config = std::make_unique<grove::JsonDataNode>("config");
}

void WebModule::setConfiguration(const grove::IDataNode& configNode,
                                  grove::IIO* io,
                                  grove::ITaskScheduler* scheduler) {
    m_io = io;
    m_config = std::make_unique<grove::JsonDataNode>("config");

    m_enabled = configNode.getBool("enabled", true);
    m_requestTimeoutMs = configNode.getInt("requestTimeoutMs", 30000);
    m_maxConcurrentRequests = configNode.getInt("maxConcurrentRequests", 10);

    // Subscribe to web requests
    if (m_io) {
        grove::SubscriptionConfig subConfig;
        m_io->subscribe("web:request", subConfig);
    }

    m_logger->info("WebModule configured (timeout: {}ms, maxConcurrent: {})",
                   m_requestTimeoutMs, m_maxConcurrentRequests);
}

const grove::IDataNode& WebModule::getConfiguration() {
    return *m_config;
}

void WebModule::process(const grove::IDataNode& input) {
    if (!m_enabled) return;
    processMessages();
}

void WebModule::processMessages() {
    if (!m_io) return;

    while (m_io->hasMessages() > 0) {
        auto msg = m_io->pullMessage();

        if (msg.topic == "web:request" && msg.data) {
            handleWebRequest(*msg.data);
        }
    }
}

void WebModule::handleWebRequest(const grove::IDataNode& request) {
    auto startTime = std::chrono::steady_clock::now();

    // Extract request parameters
    std::string requestId = request.getString("requestId", "");
    std::string url = request.getString("url", "");
    std::string method = request.getString("method", "GET");
    std::string body = request.getString("body", "");
    int timeoutMs = request.getInt("timeoutMs", m_requestTimeoutMs);

    m_totalRequests++;

    // Prepare response
    auto response = std::make_unique<grove::JsonDataNode>("response");
    response->setString("requestId", requestId);

    // Validate URL
    if (url.empty()) {
        response->setBool("success", false);
        response->setString("error", "Empty URL");
        response->setString("errorCode", "INVALID_URL");
        m_failedRequests++;
        m_io->publish("web:response", std::move(response));
        m_logger->warn("Request {} rejected: empty URL", requestId);
        return;
    }

    if (!isUrlAllowed(url)) {
        response->setBool("success", false);
        response->setString("error", "URL not allowed");
        response->setString("errorCode", "BLOCKED_URL");
        m_failedRequests++;
        m_io->publish("web:response", std::move(response));
        m_logger->warn("Request {} rejected: URL not allowed: {}", requestId, url);
        return;
    }

    try {
        // Create HttpClient with timeout
        HttpClient client(url, timeoutMs / 1000);

        // Add custom headers if provided
        auto* headersNode = const_cast<grove::IDataNode&>(request).getChildReadOnly("headers");
        if (headersNode) {
            for (const auto& key : headersNode->getChildNames()) {
                std::string value = headersNode->getString(key, "");
                client.setHeader(key, value);
            }
        }

        // Execute request based on method
        HttpResponse httpResponse;
        if (method == "GET") {
            httpResponse = client.get("/");
        } else if (method == "POST") {
            try {
                auto jsonBody = nlohmann::json::parse(body);
                httpResponse = client.post("/", jsonBody);
            } catch (const std::exception&) {
                // If body is not JSON, create a simple object
                nlohmann::json jsonBody;
                jsonBody["data"] = body;
                httpResponse = client.post("/", jsonBody);
            }
        } else {
            response->setBool("success", false);
            response->setString("error", "Unsupported method: " + method);
            response->setString("errorCode", "UNSUPPORTED_METHOD");
            m_failedRequests++;
            m_io->publish("web:response", std::move(response));
            return;
        }

        // Calculate duration
        auto endTime = std::chrono::steady_clock::now();
        auto durationMs = std::chrono::duration_cast<std::chrono::milliseconds>(
            endTime - startTime).count();

        // Build response
        response->setBool("success", httpResponse.success);
        response->setInt("statusCode", httpResponse.status);
        response->setString("body", httpResponse.body);
        response->setInt("durationMs", static_cast<int>(durationMs));

        if (httpResponse.success) {
            m_successfulRequests++;
            m_logger->debug("Request {} completed: {} {}ms",
                           requestId, httpResponse.status, durationMs);
        } else {
            m_failedRequests++;
            response->setString("error", httpResponse.error);
            response->setString("errorCode", "HTTP_ERROR");
            m_logger->warn("Request {} failed: {}", requestId, httpResponse.error);
        }

    } catch (const std::exception& e) {
        auto endTime = std::chrono::steady_clock::now();
        auto durationMs = std::chrono::duration_cast<std::chrono::milliseconds>(
            endTime - startTime).count();

        response->setBool("success", false);
        response->setString("error", e.what());
        response->setString("errorCode", "EXCEPTION");
        response->setInt("durationMs", static_cast<int>(durationMs));
        m_failedRequests++;
        m_logger->error("Request {} exception: {}", requestId, e.what());
    }

    // Publish response
    m_io->publish("web:response", std::move(response));
}

bool WebModule::isUrlAllowed(const std::string& url) {
    // Basic validation: must start with http:// or https://
    if (url.find("http://") != 0 && url.find("https://") != 0) {
        return false;
    }

    // Block localhost and private IPs for security
    std::regex privatePattern(
        R"((localhost|127\.|192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[01])\.))");
    if (std::regex_search(url, privatePattern)) {
        m_logger->warn("Blocked private/localhost URL: {}", url);
        return false;
    }

    return true;
}

std::unique_ptr<grove::IDataNode> WebModule::getHealthStatus() {
    auto health = std::make_unique<grove::JsonDataNode>("health");
    health->setBool("healthy", m_enabled);
    health->setInt("totalRequests", m_totalRequests);
    health->setInt("successfulRequests", m_successfulRequests);
    health->setInt("failedRequests", m_failedRequests);

    if (m_totalRequests > 0) {
        double successRate = (100.0 * m_successfulRequests) / m_totalRequests;
        health->setDouble("successRate", successRate);
    }

    return health;
}

void WebModule::shutdown() {
    m_logger->info("WebModule shutdown (total: {}, success: {}, failed: {})",
                   m_totalRequests, m_successfulRequests, m_failedRequests);
}

std::unique_ptr<grove::IDataNode> WebModule::getState() {
    auto state = std::make_unique<grove::JsonDataNode>("state");
    state->setInt("totalRequests", m_totalRequests);
    state->setInt("successfulRequests", m_successfulRequests);
    state->setInt("failedRequests", m_failedRequests);
    return state;
}

void WebModule::setState(const grove::IDataNode& state) {
    m_totalRequests = state.getInt("totalRequests", 0);
    m_successfulRequests = state.getInt("successfulRequests", 0);
    m_failedRequests = state.getInt("failedRequests", 0);
    m_logger->info("WebModule state restored (total: {}, success: {}, failed: {})",
                   m_totalRequests, m_successfulRequests, m_failedRequests);
}

} // namespace celuna

#ifndef CELUNA_TEST_BUILD
extern "C" {
    grove::IModule* createModule() {
        return new celuna::WebModule();
    }

    void destroyModule(grove::IModule* module) {
        delete module;
    }
}
#endif
