// Production-shaped USB binding test.
//
// The host `wifi_usb_test.cpp` uses Fake callbacks, which cannot catch the
// production bug Codex found: `UsbWiFiOps.context` was left null, so the
// dispatcher's "callback && context" guard refused every verb and the real
// device reported `wifi.status -> 0 profiles`, `wifi.list -> empty`,
// `wifi.add -> UNAVAILABLE` while the fakes passed.
//
// This test binds the REAL callbacks to a REAL WiFiProfileStore, exactly as
// `wifi_manager.cpp` does, and drives the dispatcher over the same ops struct
// production uses. It fails if the context is null or the callbacks do not
// reach the store.
#include "wifi_usb_protocol.h"
#include "wifi_profiles.h"
#include "wifi_selection.h"

#include <cassert>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>

using namespace wearable;

// The production binding: a stable context address plus callbacks that read the
// live store. This mirrors `wifi_manager.cpp`'s `WiFiManagerContext` and its
// usb_* functions.
struct WiFiManagerContext {
    uint32_t magic = 0x57494631;  // 'WIF1'
};

namespace {
MemoryWiFiStorage storage;
WiFiProfileStore store(storage);
WiFiManagerContext context;
char current_ssid[WIFI_SSID_MAX + 1] = {};
WifiLinkState state = WifiLinkState::Offline;
int persist_calls = 0;

const char *usb_state_label(void *) { return wifi_link_label(state); }
const char *usb_current_ssid(void *) { return current_ssid; }
size_t usb_profile_count(void *) { return store.count(); }
const char *usb_profile_ssid(void *, size_t index) {
    return index < store.count() ? store.at(index).ssid : "";
}
bool usb_profile_proven(void *, size_t index) {
    return index < store.count() && store.at(index).last_success != 0;
}
// Mirrors production: validate, then persist only on success.
UsbAddOutcome usb_add(void *, const char *ssid, const char *password) {
    if (!ssid || !*ssid) return UsbAddOutcome::Invalid;
    if (!wifi_ssid_valid(ssid)) return UsbAddOutcome::Invalid;
    if (!wifi_password_valid(password)) return UsbAddOutcome::Invalid;
    ++persist_calls;
    if (store.add_or_replace(ssid, password ? password : "") != WiFiStoreResult::Ok)
        return UsbAddOutcome::Rejected;
    store.mark_success(ssid, 1);
    std::strncpy(current_ssid, ssid, sizeof(current_ssid) - 1);
    state = WifiLinkState::Connected;
    return UsbAddOutcome::Verified;
}
bool usb_remove(void *, const char *ssid) { return store.remove(ssid) == WiFiStoreResult::Ok; }
bool usb_clear(void *) { return store.clear() == WiFiStoreResult::Ok; }

UsbWiFiOps production_ops() {
    return UsbWiFiOps{usb_state_label,     usb_current_ssid, usb_profile_count,
                      usb_profile_ssid,    usb_profile_proven, usb_add,
                      usb_remove,          usb_clear,        &context};
}
}  // namespace

// Extract the reply payload for a request through the real parse+handle path.
static std::string dispatch(const char *line) {
    char reply[1024];
    UsbWiFiRequest request;
    assert(usb_parse_line(line, request));
    const std::string response = usb_handle_request(request, production_ops());
    const size_t copy = response.size() < sizeof(reply) - 1 ? response.size() : sizeof(reply) - 1;
    std::memcpy(reply, response.data(), copy);
    reply[copy] = '\0';
    return std::string(reply);
}

int main() {
    store.load();
    assert(store.count() == 0);

    // A null context must make every op report empty/unavailable. This is the
    // exact production bug, encoded as a regression.
    {
        UsbWiFiOps broken = production_ops();
        broken.context = nullptr;
        UsbWiFiRequest request;
        assert(usb_parse_line("WFP/1 wifi.status", request));
        const std::string response = usb_handle_request(request, broken);
        assert(response.find("profiles=0") != std::string::npos);
        assert(response.find("state=OFFLINE") != std::string::npos);
    }

    // With the real context, status/list read the LIVE store.
    {
        UsbWiFiRequest request;
        assert(usb_parse_line("WFP/1 wifi.status", request));
        const std::string response = usb_handle_request(request, production_ops());
        // Non-null context: the real state label is used, not the null fallback.
        assert(response.find("state=OFFLINE") != std::string::npos);
        assert(response.find("WFP/1 OK wifi.status") != std::string::npos);
    }

    // Seed the store directly and prove list/status reflect it (a bound context).
    assert(store.add_or_replace("Home-WiFi", "secret1") == WiFiStoreResult::Ok);
    assert(store.mark_success("Home-WiFi", 9) == WiFiStoreResult::Ok);
    assert(store.add_or_replace("Office", "secret2") == WiFiStoreResult::Ok);
    {
        const std::string listed = dispatch("WFP/1 wifi.list");
        assert(listed.find("WFP/1 OK wifi.list") != std::string::npos);
        assert(listed.find("ssid=Home-WiFi") != std::string::npos);
        assert(listed.find("ssid=Office") != std::string::npos);
        assert(listed.find("proven=1") != std::string::npos);   // Home-WiFi
        assert(listed.find("proven=0") != std::string::npos);   // Office
        assert(listed.find("secret1") == std::string::npos);    // never a password
        assert(listed.find("secret2") == std::string::npos);
    }
    {
        const std::string status = dispatch("WFP/1 wifi.status");
        assert(status.find("profiles=2") != std::string::npos);
    }

    // wifi.add runs the real candidate path and persists on success.
    {
        const int before = persist_calls;
        const std::string added = dispatch("WFP/1 wifi.add ssid=Cafe password=latte123");
        assert(added.find("result=VERIFIED") != std::string::npos);
        assert(persist_calls == before + 1);
        assert(store.find("Cafe") != nullptr);
        assert(added.find("latte123") == std::string::npos);  // never echoed
    }

    // wifi.remove and wifi.clear reach the live store.
    {
        const std::string removed = dispatch("WFP/1 wifi.remove ssid=Office");
        assert(removed.find("WFP/1 OK wifi.remove") != std::string::npos);
        assert(store.find("Office") == nullptr);
        const std::string cleared = dispatch("WFP/1 wifi.clear");
        assert(cleared.find("WFP/1 OK wifi.clear") != std::string::npos);
        assert(store.count() == 0);
    }

    std::puts("USB production binding: real context reaches live store "
              "(status/list/add/remove/clear) PASS (host)");
    return 0;
}