#include "engine_security.h"

#include <sstream>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <thread>
#include <iomanip>
#include <ctime>
#include <arpa/inet.h>

namespace node669427 {

static std::string json_escape(const std::string& s) {
    std::string result;
    result.reserve(s.size() + 8);
    for (char c : s) {
        switch (c) {
            case '"':  result += "\\\""; break;
            case '\\': result += "\\\\"; break;
            case '\n': result += "\\n"; break;
            case '\r': result += "\\r"; break;
            case '\t': result += "\\t"; break;
            default:
                if (static_cast<unsigned char>(c) < 0x20) {
                    char buf[8];
                    snprintf(buf, sizeof(buf), "\\u%04x", static_cast<unsigned char>(c));
                    result += buf;
                } else {
                    result += c;
                }
        }
    }
    return result;
}

static bool is_wireguard_ip(const std::string& ip) {
    if (ip == "127.0.0.1") return true;
    
    struct in_addr addr;
    if (inet_pton(AF_INET, ip.c_str(), &addr) != 1) return false;
    uint32_t ip_num = ntohl(addr.s_addr);
    
    // 172.28.41.0/24 (rain)
    uint32_t wg_pay_net = 0xAC1C2900;
    // 10.69.69.0/24 (wg10)
    uint32_t wg10_net = 0x0A454500;
    uint32_t mask24 = 0xFFFFFF00;
    
    if ((ip_num & mask24) == wg_pay_net) return true;
    if ((ip_num & mask24) == wg10_net) return true;
    
    return false;
}

SecurityEngine::SecurityEngine() {}

bool SecurityEngine::checkRateLimit(const std::string& ip) {
    std::lock_guard<std::mutex> lock(sessions_mutex_);
    auto& session = sessions_[ip];
    auto now = std::chrono::system_clock::now();
    
    if (session.first_seen == std::chrono::system_clock::time_point{}) {
        session.tokens = 10.0; // BURST
        session.last_token_update = now;
    }
    
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - session.last_token_update).count();
    double added_tokens = (static_cast<double>(duration) / 1000.0) * (60.0 / 60.0); // 60 req/min = 1 req/sec
    
    session.tokens = std::min(10.0, session.tokens + added_tokens);
    session.last_token_update = now;
    
    if (session.tokens >= 1.0) {
        session.tokens -= 1.0;
        return true;
    }
    return false;
}

void SecurityEngine::recordRequest(const std::string& ip, const std::string& path) {
    if (!is_wireguard_ip(ip)) {
        logEvent("access_denied", ip, "high", "Non-WireGuard IP attempted access");
        return;
    }

    std::lock_guard<std::mutex> lock(sessions_mutex_);
    total_requests_++;

    auto now = std::chrono::system_clock::now();
    auto& session = sessions_[ip];
    session.ip = ip;
    session.request_count++;
    session.last_seen = now;
    if (session.first_seen == std::chrono::system_clock::time_point{}) {
        session.first_seen = now;
    }
    session.path_history.push_back(path);
    if (session.path_history.size() > 100) {
        session.path_history.erase(session.path_history.begin());
    }

    detectAnomaly(session, path);
}

void SecurityEngine::detectAnomaly(Session& session, const std::string& path) {
    double score_change = 0.0;

    auto path_count = std::count(session.path_history.begin(),
                                  session.path_history.end(), path);
    if (path_count > 20) {
        score_change -= 5.0;
    }

    if (session.request_count > 1000) {
        auto duration = std::chrono::duration_cast<std::chrono::seconds>(
            session.last_seen - session.first_seen).count();
        if (duration > 0) {
            double rps = static_cast<double>(session.request_count) / static_cast<double>(duration);
            if (rps > 50.0) score_change -= 10.0;
            else if (rps > 20.0) score_change -= 5.0;
        }
    }

    if (path.find("/admin") != std::string::npos ||
        path.find("/.env") != std::string::npos ||
        path.find("/wp-") != std::string::npos ||
        path.find("/config") != std::string::npos) {
        score_change -= 15.0;
    }

    if (path == "/health" || path == "/status") {
        score_change += 0.5;
    }

    session.trust_score = std::clamp(session.trust_score + score_change, 0.0, 100.0);

    if (session.trust_score < 10.0) {
        if (!session.blocked) {
            session.blocked = true;
            session.strikes++;
            logEvent("auto_block", session.ip, "high",
                     "trust_score=" + std::to_string(session.trust_score));
        }
        blocked_requests_++;
    } else if (session.trust_score < 25.0) {
        session.strikes++;
    }
}

void SecurityEngine::addStrike(const std::string& ip) {
    std::lock_guard<std::mutex> lock(sessions_mutex_);
    auto it = sessions_.find(ip);
    if (it != sessions_.end()) {
        it->second.strikes++;
        it->second.trust_score -= 5.0;
        if (it->second.trust_score < 0) it->second.trust_score = 0;
        if (it->second.strikes >= 3) {
            it->second.blocked = true;
            logEvent("auto_block", ip, "high", "3 strikes reached");
        }
    }
}

void SecurityEngine::logEvent(const std::string& type, const std::string& source_ip,
                               const std::string& severity, const std::string& details) {
    std::lock_guard<std::mutex> lock(events_mutex_);
    auto now = std::chrono::system_clock::now();
    auto t = std::chrono::system_clock::to_time_t(now);
    char buf[64];
    std::strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", std::gmtime(&t));

    SecurityEvent ev;
    ev.type = type;
    ev.source_ip = source_ip;
    ev.severity = severity;
    ev.timestamp = buf;
    ev.details = details;
    events_.push_back(ev);

    while (events_.size() > MAX_EVENTS) {
        events_.erase(events_.begin());
    }
}

bool SecurityEngine::isBlocked(const std::string& ip) const {
    if (!is_wireguard_ip(ip)) return true;
    
    std::lock_guard<std::mutex> lock(sessions_mutex_);
    auto it = sessions_.find(ip);
    return (it != sessions_.end() && it->second.blocked);
}

double SecurityEngine::getTrustScore(const std::string& ip) const {
    std::lock_guard<std::mutex> lock(sessions_mutex_);
    auto it = sessions_.find(ip);
    return (it != sessions_.end()) ? it->second.trust_score : 50.0;
}

std::string SecurityEngine::getEvents() const {
    std::lock_guard<std::mutex> lock(events_mutex_);
    std::ostringstream json;
    json << "[";
    size_t limit = std::min(events_.size(), size_t(100));
    for (size_t i = 0; i < limit; ++i) {
        if (i > 0) json << ",";
        json << "{\"type\":\"" << json_escape(events_[events_.size() - limit + i].type) << "\""
             << ",\"source_ip\":\"" << json_escape(events_[events_.size() - limit + i].source_ip) << "\""
             << ",\"severity\":\"" << json_escape(events_[events_.size() - limit + i].severity) << "\""
             << ",\"timestamp\":\"" << json_escape(events_[events_.size() - limit + i].timestamp) << "\"}";
    }
    json << "]";
    return json.str();
}

std::string SecurityEngine::getSessionStatus(const std::string& ip) const {
    std::lock_guard<std::mutex> lock(sessions_mutex_);
    auto it = sessions_.find(ip);
    if (it == sessions_.end()) {
        return "{\"ip\":\"" + ip + "\",\"known\":false}";
    }
    std::ostringstream json;
    json << "{\"ip\":\"" << it->second.ip << "\""
         << ",\"trust_score\":" << it->second.trust_score
         << ",\"strikes\":" << it->second.strikes
         << ",\"requests\":" << it->second.request_count
         << ",\"blocked\":" << (it->second.blocked ? "true" : "false") << "}";
    return json.str();
}

SecurityMetrics SecurityEngine::getMetrics() const {
    SecurityMetrics m;
    m.total_requests = total_requests_.load();
    m.blocked_requests = blocked_requests_.load();

    std::lock_guard<std::mutex> lock(sessions_mutex_);
    m.active_sessions = sessions_.size();

    double total = 0.0;
    for (const auto& [ip, s] : sessions_) {
        total += s.trust_score;
    }
    m.avg_trust_score = sessions_.empty() ? 50.0 : total / static_cast<double>(sessions_.size());
    return m;
}

void SecurityEngine::decayScores() {
    std::lock_guard<std::mutex> lock(sessions_mutex_);
    auto now = std::chrono::system_clock::now();
    std::vector<std::string> to_remove;
    for (auto& [ip, session] : sessions_) {
        auto idle = std::chrono::duration_cast<std::chrono::minutes>(now - session.last_seen).count();
        if (idle > 5) {
            session.trust_score = std::min(session.trust_score + 2.0, 100.0);
        }
        if (idle > 30 && !session.blocked) {
            to_remove.push_back(ip);
        }
    }
    for (const auto& ip : to_remove) {
        sessions_.erase(ip);
    }
}

void SecurityEngine::run() {
    while (running_.load()) {
        decayScores();
        std::this_thread::sleep_for(std::chrono::seconds(30));
    }
}

void SecurityEngine::stop() {
    running_.store(false);
}

} // namespace node669427
