// ============================================================================
// wifi_security_scan.cpp — see wifi_security_scan.h for design notes.
// ============================================================================

#include "wifi_security_scan.h"
#include "network_id.h"

#include <cstring>

#ifdef ARDUINO
#include <Arduino.h>
static inline uint32_t wsec_now_ms() { return millis(); }
#else
#include <chrono>
static inline uint32_t wsec_now_ms() {
    using namespace std::chrono;
    static auto start = steady_clock::now();
    return (uint32_t)duration_cast<milliseconds>(steady_clock::now() - start).count();
}
#endif

#define WSEC_MAX_FINDINGS 32
#define WSEC_DEAUTH_WINDOW_MS   60000u
#define WSEC_DEAUTH_FLOOD_THRESHOLD  50u   // frames/60s -- far above normal background rate

static WifiSecurityFinding s_findings[WSEC_MAX_FINDINGS];
static size_t s_finding_count = 0;
static WifiSecurityStats s_stats{};

// Rolling deauth/disassoc counters (reset every WSEC_DEAUTH_WINDOW_MS)
static uint32_t s_deauth_count = 0;
static uint32_t s_disassoc_count = 0;
static uint32_t s_window_start_ms = 0;

void wifi_security_scan_init() {
    s_finding_count = 0;
    s_stats = WifiSecurityStats{};
    s_window_start_ms = wsec_now_ms();
}

void wifi_security_scan_process_frame(const uint8_t* frame, size_t len) {
    if (!frame || len < 1) return;
    uint8_t frame_ctrl = frame[0];
    if (frame_ctrl == 0xC0) {          // type=0 mgmt, subtype=0x0C (deauth)
        s_deauth_count++;
    } else if (frame_ctrl == 0xA0) {   // type=0 mgmt, subtype=0x0A (disassoc)
        s_disassoc_count++;
    }

    uint32_t now = wsec_now_ms();
    if (now - s_window_start_ms >= WSEC_DEAUTH_WINDOW_MS) {
        s_stats.deauth_frames_last_60s = s_deauth_count;
        s_stats.disassoc_frames_last_60s = s_disassoc_count;
        s_stats.deauth_flood_alert =
            (s_deauth_count + s_disassoc_count) >= WSEC_DEAUTH_FLOOD_THRESHOLD;
        s_deauth_count = 0;
        s_disassoc_count = 0;
        s_window_start_ms = now;
    }
}

void wifi_security_scan_tick() {
    s_finding_count = 0;
    uint16_t open_n = 0, wep_n = 0, tkip_only_n = 0, evil_twin_n = 0;

    int count = network_id_count();
    if (count <= 0) return;

    // Build a small local snapshot so we can compare SSIDs pairwise for
    // evil-twin detection without repeatedly re-querying network_id.
    struct Snap { NetworkEntry e; };
    static Snap snap[NETWORK_ID_TABLE_SIZE];
    int n = 0;
    for (int i = 0; i < count && i < NETWORK_ID_TABLE_SIZE; i++) {
        if (network_id_get(i, &snap[n].e)) n++;
    }

    for (int i = 0; i < n; i++) {
        const NetworkEntry& e = snap[i].e;
        WifiSecurityIssue issue = WIFI_SEC_ISSUE_NONE;

        if (e.cipher_id & CIPHER_OPEN) {
            issue = WIFI_SEC_ISSUE_OPEN;
            open_n++;
        } else if (e.cipher_id & CIPHER_WEP) {
            issue = WIFI_SEC_ISSUE_WEP;
            wep_n++;
        } else if ((e.cipher_id & CIPHER_TKIP) && !(e.cipher_id & CIPHER_CCMP)) {
            issue = WIFI_SEC_ISSUE_WEAK_TKIP_ONLY;
            tkip_only_n++;
        }

        // Evil-twin suspicion: same non-empty SSID, different BSSID, AND a
        // different security posture -- a legitimate multi-AP mesh/extender
        // deployment normally shares one consistent security posture across
        // all its APs, so this specific combination (same name, different
        // BSSID, different cipher) is a much rarer and more suspicious
        // signal than a bare SSID match would be on its own.
        if (issue == WIFI_SEC_ISSUE_NONE && e.ssid[0] != '\0') {
            for (int j = 0; j < n; j++) {
                if (j == i) continue;
                const NetworkEntry& other = snap[j].e;
                if (other.ssid[0] == '\0') continue;
                if (strncmp(e.ssid, other.ssid, sizeof(e.ssid)) != 0) continue;
                if (memcmp(e.bssid, other.bssid, 6) == 0) continue;   // same AP
                if (e.cipher_id != other.cipher_id) {
                    issue = WIFI_SEC_ISSUE_EVIL_TWIN_SUSPECT;
                    evil_twin_n++;
                    break;
                }
            }
        }

        if (issue != WIFI_SEC_ISSUE_NONE && s_finding_count < WSEC_MAX_FINDINGS) {
            WifiSecurityFinding& f = s_findings[s_finding_count++];
            memcpy(f.bssid, e.bssid, 6);
            strncpy(f.ssid, e.ssid, sizeof(f.ssid) - 1);
            f.ssid[sizeof(f.ssid) - 1] = '\0';
            f.issue = issue;
            f.last_rssi = e.last_rssi;
        }
    }

    s_stats.open_networks = open_n;
    s_stats.wep_networks = wep_n;
    s_stats.weak_tkip_only_networks = tkip_only_n;
    s_stats.evil_twin_suspects = evil_twin_n;
}

void wifi_security_scan_get_stats(WifiSecurityStats* out) {
    if (out) *out = s_stats;
}

size_t wifi_security_scan_list_findings(WifiSecurityFinding* out, size_t max) {
    if (!out) return 0;
    size_t n = s_finding_count < max ? s_finding_count : max;
    memcpy(out, s_findings, n * sizeof(WifiSecurityFinding));
    return n;
}
