// USB provisioning protocol framing, value encoding, parsing and redaction.
#include "wifi_usb_protocol.h"
#include <cassert>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>

using namespace wearable;

// A small in-memory ops implementation that records calls and never stores a
// password outside the callback.
struct Fake {
    std::vector<UsbWiFiProfile> profiles;
    std::vector<std::pair<std::string, std::string>> added;  // ssid, password
    std::vector<std::string> removed;
    int cleared = 0;
    UsbAddOutcome outcome = UsbAddOutcome::Verified;
    const char *state = "CONNECTED";
    const char *current = "Home-WiFi";
};

static const char *f_state(void *ctx) { return static_cast<Fake *>(ctx)->state; }
static const char *f_current(void *ctx) { return static_cast<Fake *>(ctx)->current; }
static size_t f_count(void *ctx) { return static_cast<Fake *>(ctx)->profiles.size(); }
static const char *f_ssid(void *ctx, size_t i) { return static_cast<Fake *>(ctx)->profiles[i].ssid.c_str(); }
static bool f_proven(void *ctx, size_t i) { return static_cast<Fake *>(ctx)->profiles[i].proven; }
static UsbAddOutcome f_add(void *ctx, const char *ssid, const char *password) {
    auto *fake = static_cast<Fake *>(ctx);
    fake->added.emplace_back(ssid, password);
    // Mirror the firmware contract: only a verified candidate is persisted.
    if (fake->outcome == UsbAddOutcome::Verified)
        fake->profiles.push_back({ssid, true});
    return fake->outcome;
}
static bool f_remove(void *ctx, const char *ssid) {
    static_cast<Fake *>(ctx)->removed.emplace_back(ssid);
    return true;
}
static bool f_clear(void *ctx) { ++static_cast<Fake *>(ctx)->cleared; return true; }

static UsbWiFiOps ops_for(Fake &fake) {
    return UsbWiFiOps{f_state, f_current, f_count, f_ssid, f_proven,
                      f_add, f_remove, f_clear, &fake};
}

int main() {
    // ---- Value encoding is a strict, reversible round trip ----------------
    const char *round_trips[] = {
        "", "Home-WiFi", "My Home WiFi", "hello world", "a=b%c&d", "Powell Home",
        "p@ss#w/rd?", "under_score-dot.", "+plus+", "semi;colon", "quote\"and'apos",
        "tab\tchar", "new\nline", "中文网络", "café-☕", "100% pure", "a&b=c",
    };
    for (const char *value : round_trips) {
        const std::string encoded = usb_encode_value(value);
        const std::string decoded = usb_decode_value(encoded);
        assert(decoded == value);
        // An encoded value must never contain a separator or a raw '%'.
        assert(encoded.find(' ') == std::string::npos);
        assert(encoded.find('&') == std::string::npos);
        assert(encoded.find('=') == std::string::npos);
        // It may contain '%' only as an escape introducer.
        for (size_t i = 0; i < encoded.size(); ++i)
            if (encoded[i] == '%')
                assert(i + 2 < encoded.size() && std::isxdigit((unsigned char)encoded[i + 1]) &&
                       std::isxdigit((unsigned char)encoded[i + 2]));
    }
    // A space is always %20, never '+', and '+' is always %2B.
    assert(usb_encode_value("a b") == "a%20b");
    assert(usb_encode_value("a+b") == "a%2Bb");
    assert(usb_decode_value("a%20b") == "a b" && usb_decode_value("a+b") == "a+b");
    // UTF-8 bytes round-trip exactly.
    assert(usb_encode_value("中文") == "%E4%B8%AD%E6%96%87");

    // ---- Framing and version --------------------------------------------
    UsbWiFiRequest request;
    assert(!usb_parse_line(nullptr, request));
    assert(!usb_parse_line("garbage", request));
    assert(!usb_parse_line("WFP/2 wifi.list", request));
    assert(usb_parse_line("WFP/1 wifi.list", request));
    assert(request.valid && request.verb == UsbWiFiRequest::Verb::List);
    assert(usb_parse_line("WFP/1 nonsense", request));
    assert(request.verb == UsbWiFiRequest::Verb::Unknown);

    Fake fake;
    fake.profiles = {{"Home-WiFi", true}, {"Office", false}, {"Phone-Hotspot", true}};
    const auto ops = ops_for(fake);

    // ---- status ---------------------------------------------------------
    assert(usb_parse_line("WFP/1 wifi.status", request));
    {
        const std::string response = usb_handle_request(request, ops);
        assert(response.find("WFP/1 OK wifi.status state=CONNECTED ssid=Home-WiFi profiles=3") != std::string::npos);
        assert(response.find("WFP/1 END") != std::string::npos);
    }

    // ---- list -----------------------------------------------------------
    assert(usb_parse_line("WFP/1 wifi.list", request));
    {
        const std::string response = usb_handle_request(request, ops);
        assert(response.find("WFP/1 DATA index=0 ssid=Home-WiFi proven=1") != std::string::npos);
        assert(response.find("WFP/1 DATA index=1 ssid=Office proven=0") != std::string::npos);
        assert(response.find("WFP/1 DATA index=2 ssid=Phone-Hotspot proven=1") != std::string::npos);
        assert(response.find("WFP/1 END") != std::string::npos);
    }
    // An SSID with a space still yields exactly one DATA token.
    fake.profiles.push_back({"My Home WiFi", false});
    assert(usb_parse_line("WFP/1 wifi.list", request));
    {
        const std::string response = usb_handle_request(request, ops);
        assert(response.find("ssid=My%20Home%20WiFi proven=0") != std::string::npos);
    }
    fake.profiles.pop_back();

    // ---- add: encoding round-trips spaces and separators ----------------
    assert(usb_parse_line("WFP/1 wifi.add ssid=Powell%20Home password=hello%20world", request));
    assert(request.verb == UsbWiFiRequest::Verb::Add);
    assert(request.ssid == "Powell Home");
    assert(request.has_password && request.password == "hello world");
    {
        const std::string response = usb_handle_request(request, ops);
        assert(fake.added.size() == 1 && fake.added[0].first == "Powell Home");
        assert(fake.added[0].second == "hello world");
        // The response is encoded and contains no password.
        assert(response.find("ssid=Powell%20Home") != std::string::npos);
        assert(response.find("result=VERIFIED") != std::string::npos);
        assert(response.find("hello") == std::string::npos);
        assert(response.find("password") == std::string::npos);
        assert(response.find("WFP/1 END") != std::string::npos);
        assert(usb_line_contains_password("WFP/1 wifi.add ssid=x password=y"));
        assert(!usb_line_contains_password("WFP/1 wifi.add ssid=x"));
    }
    // Separator characters survive the round trip through the wire format.
    assert(usb_parse_line("WFP/1 wifi.add ssid=a%3Db%25c%26d password=p%3D1%262", request));
    assert(request.ssid == "a=b%c&d" && request.password == "p=1&2");
    fake.added.clear();
    { usb_handle_request(request, ops); assert(fake.added[0].first == "a=b%c&d"); }

    // Missing SSID is a specific error, not a silent no-op.
    assert(usb_parse_line("WFP/1 wifi.add password=x", request));
    {
        const std::string response = usb_handle_request(request, ops);
        assert(response.find("WFP/1 ERROR wifi.add reason=ssid-required") != std::string::npos);
    }

    // ---- add: candidate-first verdicts are reported honestly -------------
    // A failed candidate persists nothing and reports REJECTED.
    {
        Fake f;
        f.profiles = {{"Existing", true}};
        f.outcome = UsbAddOutcome::Rejected;
        const auto o = ops_for(f);
        assert(usb_parse_line("WFP/1 wifi.add ssid=NewNet password=wrong", request));
        const std::string response = usb_handle_request(request, o);
        assert(response.find("WFP/1 ERROR wifi.add ssid=NewNet result=REJECTED") != std::string::npos);
        // Every existing profile is preserved; the candidate was not added.
        assert(f.profiles.size() == 1 && f.profiles[0].ssid == "Existing");
        assert(f.added.size() == 1);  // the callback still saw the candidate
    }
    // A valid candidate is persisted only after validation and reports VERIFIED.
    {
        Fake f;
        f.profiles = {{"Existing", true}};
        f.outcome = UsbAddOutcome::Verified;
        const auto o = ops_for(f);
        assert(usb_parse_line("WFP/1 wifi.add ssid=NewNet password=right", request));
        const std::string response = usb_handle_request(request, o);
        assert(response.find("WFP/1 OK wifi.add ssid=NewNet result=VERIFIED") != std::string::npos);
        assert(f.profiles.size() == 2 && f.profiles[1].ssid == "NewNet");
    }
    // If validation cannot run, the verb says so instead of claiming success.
    {
        Fake f;
        f.profiles = {{"Existing", true}};
        f.outcome = UsbAddOutcome::Unavailable;
        const auto o = ops_for(f);
        assert(usb_parse_line("WFP/1 wifi.add ssid=NewNet password=?", request));
        const std::string response = usb_handle_request(request, o);
        assert(response.find("WFP/1 ERROR wifi.add ssid=NewNet result=UNAVAILABLE") != std::string::npos);
        assert(f.profiles.size() == 1);
    }
    // Connected but not saved is distinct from both.
    {
        Fake f;
        f.outcome = UsbAddOutcome::StorageError;
        const auto o = ops_for(f);
        assert(usb_parse_line("WFP/1 wifi.add ssid=X password=y", request));
        const std::string response = usb_handle_request(request, o);
        assert(response.find("result=STORAGE_ERROR") != std::string::npos);
    }

    // ---- remove ---------------------------------------------------------
    assert(usb_parse_line("WFP/1 wifi.remove ssid=My%20Office", request));
    assert(request.ssid == "My Office");
    {
        const std::string response = usb_handle_request(request, ops);
        assert(fake.removed.back() == "My Office");
        assert(response.find("WFP/1 OK wifi.remove ssid=My%20Office") != std::string::npos);
    }
    assert(usb_parse_line("WFP/1 wifi.remove", request));
    {
        const std::string response = usb_handle_request(request, ops);
        assert(response.find("wifi.remove reason=ssid-required") != std::string::npos);
    }

    // ---- clear ----------------------------------------------------------
    assert(usb_parse_line("WFP/1 wifi.clear", request));
    {
        const std::string response = usb_handle_request(request, ops);
        assert(fake.cleared == 1);
        assert(response.find("WFP/1 OK wifi.clear") != std::string::npos);
    }

    // ---- unknown verb ---------------------------------------------------
    assert(usb_parse_line("WFP/1 wifi.explode", request));
    {
        const std::string response = usb_handle_request(request, ops);
        assert(response.find("WFP/1 ERROR unknown-verb") != std::string::npos);
    }

    // ---- ops failures surface as ERROR ---------------------------------
    UsbWiFiOps broken{};
    assert(usb_parse_line("WFP/1 wifi.add ssid=Y password=z", request));
    {
        const std::string response = usb_handle_request(request, broken);
        // A null add callback cannot validate, so it must report UNAVAILABLE.
        assert(response.find("WFP/1 ERROR wifi.add") != std::string::npos);
        assert(response.find("result=UNAVAILABLE") != std::string::npos);
    }
    assert(usb_parse_line("WFP/1 wifi.clear", request));
    assert(usb_handle_request(request, broken).find("WFP/1 ERROR wifi.clear") != std::string::npos);

    std::puts("USB Wi-Fi protocol encoding, candidate-first verdicts, redaction PASS");
}