#pragma once
// Pure USB provisioning command protocol.
//
// Versioned, framed, newline-delimited. This header holds only parsing and
// response formatting so it can be host-tested without ESP-IDF. The firmware
// supplies the profile-store operations through a small callback interface.
//
// Wire format:
//   Request :  WFP/1 <verb> [key=<percent-encoded value> ...]
//   Response:  WFP/1 <OK|ERROR> <verb> [key=value ...]
//              WFP/1 DATA <key=value ...>      (zero or more)
//              WFP/1 END
//
// Value encoding (reversible, UTF-8 safe):
//   Every byte outside the RFC 3986 unreserved set [A-Za-z0-9-._~] is written
//   as "%" followed by two uppercase hex digits. Space, "=", "&", "%", "+" and
//   all non-ASCII bytes are therefore always encoded, so a value can never be
//   confused with a separator or split on a space. `+` is NOT used for space;
//   decoding is an exact inverse of encoding.
//
// Security: a `password=` value is accepted on input but is never echoed in any
// response line. `usb_line_contains_password` lets tests prove that.
#include <cctype>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>

namespace wearable {

constexpr const char *WIFI_USB_PREFIX = "WFP/1 ";

// ---- Value encoding --------------------------------------------------------

// Percent-encode one value. Only unreserved characters pass through, so the
// result never contains a space, '=', '&', '#', '%' or any control/non-ASCII
// byte. The output is always longer than or equal to the input for ASCII, and
// at most 3x for fully non-ASCII input.
inline std::string usb_encode_value(const std::string &raw) {
    static const char *kHex = "0123456789ABCDEF";
    std::string out;
    out.reserve(raw.size());
    for (unsigned char byte : raw) {
        const bool unreserved = (byte >= 'A' && byte <= 'Z') || (byte >= 'a' && byte <= 'z') ||
                                (byte >= '0' && byte <= '9') || byte == '-' || byte == '.' ||
                                byte == '_' || byte == '~';
        if (unreserved) {
            out += char(byte);
        } else {
            out += '%';
            out += kHex[byte >> 4];
            out += kHex[byte & 0x0f];
        }
    }
    return out;
}

// Decode a percent-encoded value. Invalid escapes are preserved literally
// rather than dropped, so a malformed token cannot silently change meaning.
inline std::string usb_decode_value(const std::string &encoded) {
    auto hex = [](char c) -> int {
        if (c >= '0' && c <= '9') return c - '0';
        if (c >= 'a' && c <= 'f') return c - 'a' + 10;
        if (c >= 'A' && c <= 'F') return c - 'A' + 10;
        return -1;
    };
    std::string out;
    out.reserve(encoded.size());
    for (size_t i = 0; i < encoded.size(); ++i) {
        if (encoded[i] == '%' && i + 2 < encoded.size()) {
            const int hi = hex(encoded[i + 1]);
            const int lo = hex(encoded[i + 2]);
            if (hi >= 0 && lo >= 0) {
                out += char((hi << 4) | lo);
                i += 2;
                continue;
            }
        }
        out += encoded[i];
    }
    return out;
}

// ---- Request / response types ---------------------------------------------

// Outcome of an add attempt. The wire response reports the same verdict, so the
// CLI never claims a credential was validated when it was not.
enum class UsbAddOutcome : uint8_t {
    Verified,    // candidate connected; profile persisted
    Rejected,    // candidate did not connect; nothing persisted
    Unavailable, // no way to run a connection test right now
    Invalid,     // malformed or rejected credential
    StorageError // connected but could not persist
};

constexpr const char *usb_add_outcome_label(UsbAddOutcome outcome) {
    switch (outcome) {
    case UsbAddOutcome::Verified: return "VERIFIED";
    case UsbAddOutcome::Rejected: return "REJECTED";
    case UsbAddOutcome::Unavailable: return "UNAVAILABLE";
    case UsbAddOutcome::Invalid: return "INVALID";
    case UsbAddOutcome::StorageError: return "STORAGE_ERROR";
    default: return "UNKNOWN";
    }
}

struct UsbWiFiRequest {
    enum class Verb { Unknown, Status, List, Add, Remove, Clear };
    Verb verb = Verb::Unknown;
    std::string ssid;
    bool has_password = false;
    std::string password;  // accepted for use, never echoed
    bool valid = false;    // framing/version parsed
};

// A single known-network row, as the protocol needs it.
struct UsbWiFiProfile {
    std::string ssid;
    bool proven = false;
};

// Operations the firmware implements. All are optional; a null callback makes
// that verb return an error without touching the device.
struct UsbWiFiOps {
    const char *(*state_label)(void *) = nullptr;
    const char *(*current_ssid)(void *) = nullptr;
    size_t (*profile_count)(void *) = nullptr;
    const char *(*profile_ssid)(void *, size_t) = nullptr;
    bool (*profile_proven)(void *, size_t) = nullptr;
    // Candidate-first add: the implementation must validate the credentials
    // before persisting them. Returning Verified means the profile is durable.
    UsbAddOutcome (*add)(void *, const char *ssid, const char *password) = nullptr;
    bool (*remove)(void *, const char *ssid) = nullptr;
    bool (*clear)(void *) = nullptr;
    void *context = nullptr;
};

// ---- Parsing ---------------------------------------------------------------

// Parse one request line. Values are percent-decoded; the line is split on
// literal spaces only, which cannot occur inside an encoded value.
inline bool usb_parse_line(const char *line, UsbWiFiRequest &out) {
    out = UsbWiFiRequest{};
    if (!line) return false;
    const size_t prefix = std::strlen(WIFI_USB_PREFIX);
    if (std::strncmp(line, WIFI_USB_PREFIX, prefix) != 0) return false;
    const char *rest = line + prefix;
    const char *space = std::strchr(rest, ' ');
    const size_t verb_length = space ? size_t(space - rest) : std::strlen(rest);
    const std::string verb(rest, verb_length);
    const char *args = space ? space + 1 : rest + verb_length;
    out.valid = true;
    if (verb == "wifi.status") out.verb = UsbWiFiRequest::Verb::Status;
    else if (verb == "wifi.list") out.verb = UsbWiFiRequest::Verb::List;
    else if (verb == "wifi.add") out.verb = UsbWiFiRequest::Verb::Add;
    else if (verb == "wifi.remove") out.verb = UsbWiFiRequest::Verb::Remove;
    else if (verb == "wifi.clear") out.verb = UsbWiFiRequest::Verb::Clear;
    else { out.verb = UsbWiFiRequest::Verb::Unknown; return true; }

    // Collect key=value pairs. Values run to the next literal space.
    for (const char *p = args; p && *p;) {
        const char *next = std::strchr(p, ' ');
        const size_t length = next ? size_t(next - p) : std::strlen(p);
        if (length > 5 && std::strncmp(p, "ssid=", 5) == 0)
            out.ssid = usb_decode_value(std::string(p + 5, length - 5));
        else if (length >= 9 && std::strncmp(p, "password=", 9) == 0) {
            out.has_password = true;
            out.password = usb_decode_value(std::string(p + 9, length - 9));
        }
        if (!next) break;
        p = next + 1;
    }
    return true;
}

inline bool usb_line_contains_password(const char *line) {
    if (!line) return false;
    for (const char *p = line; *p; ++p)
        if (std::strncmp(p, "password=", 9) == 0) return true;
    return false;
}

inline std::string usb_verb_name(UsbWiFiRequest::Verb verb) {
    switch (verb) {
    case UsbWiFiRequest::Verb::Status: return "wifi.status";
    case UsbWiFiRequest::Verb::List: return "wifi.list";
    case UsbWiFiRequest::Verb::Add: return "wifi.add";
    case UsbWiFiRequest::Verb::Remove: return "wifi.remove";
    case UsbWiFiRequest::Verb::Clear: return "wifi.clear";
    default: return "unknown";
    }
}

// ---- Response building -----------------------------------------------------

// Build the full framed response for one request. Never includes a password.
inline std::string usb_handle_request(const UsbWiFiRequest &request, const UsbWiFiOps &ops) {
    std::string out;
    auto line = [&](const std::string &text) { out += text; out += '\n'; };
    if (!request.valid) { line("WFP/1 ERROR version"); return out; }
    if (request.verb == UsbWiFiRequest::Verb::Unknown) {
        line("WFP/1 ERROR unknown-verb");
        return out;
    }
    if (request.verb == UsbWiFiRequest::Verb::Status) {
        const char *state = ops.state_label && ops.context ? ops.state_label(ops.context) : "OFFLINE";
        const char *ssid = ops.current_ssid && ops.context ? ops.current_ssid(ops.context) : "";
        const size_t count = ops.profile_count && ops.context ? ops.profile_count(ops.context) : 0;
        // The current SSID is untrusted input on the wire; encode it so the
        // status line stays a single, unambiguous key=value token.
        const std::string encoded = (*ssid) ? usb_encode_value(ssid) : "-";
        line(std::string("WFP/1 OK wifi.status state=") + state + " ssid=" + encoded +
             " profiles=" + std::to_string(count));
        line("WFP/1 END");
        return out;
    }
    if (request.verb == UsbWiFiRequest::Verb::List) {
        line("WFP/1 OK wifi.list");
        const size_t count = ops.profile_count && ops.context ? ops.profile_count(ops.context) : 0;
        for (size_t i = 0; i < count; ++i) {
            const char *ssid = ops.profile_ssid && ops.context ? ops.profile_ssid(ops.context, i) : "";
            const bool proven = ops.profile_proven && ops.context ? ops.profile_proven(ops.context, i) : false;
            // Only the SSID and proven flag; never a password. The SSID is
            // percent-encoded so a space or '&' cannot break the DATA line.
            line(std::string("WFP/1 DATA index=") + std::to_string(i) + " ssid=" +
                 (ssid ? usb_encode_value(ssid) : std::string()) + " proven=" + (proven ? "1" : "0"));
        }
        line("WFP/1 END");
        return out;
    }
    if (request.verb == UsbWiFiRequest::Verb::Add) {
        if (request.ssid.empty()) {
            line("WFP/1 ERROR wifi.add reason=ssid-required");
            return out;
        }
        // Candidate-first: the implementation validates before persisting.
        const UsbAddOutcome outcome = ops.add && ops.context
            ? ops.add(ops.context, request.ssid.c_str(), request.password.c_str())
            : UsbAddOutcome::Unavailable;
        const bool ok = outcome == UsbAddOutcome::Verified;
        const std::string encoded_ssid = usb_encode_value(request.ssid);
        // The verdict is explicit and never overstates validation.
        line(std::string("WFP/1 ") + (ok ? "OK" : "ERROR") + " wifi.add ssid=" + encoded_ssid +
             " result=" + usb_add_outcome_label(outcome));
        line("WFP/1 END");
        return out;
    }
    if (request.verb == UsbWiFiRequest::Verb::Remove) {
        if (request.ssid.empty()) {
            line("WFP/1 ERROR wifi.remove reason=ssid-required");
            return out;
        }
        const bool ok = ops.remove && ops.context ? ops.remove(ops.context, request.ssid.c_str()) : false;
        line(std::string("WFP/1 ") + (ok ? "OK" : "ERROR") + " wifi.remove ssid=" +
             usb_encode_value(request.ssid));
        line("WFP/1 END");
        return out;
    }
    // Clear
    const bool ok = ops.clear && ops.context ? ops.clear(ops.context) : false;
    line(std::string("WFP/1 ") + (ok ? "OK" : "ERROR") + " wifi.clear");
    line("WFP/1 END");
    return out;
}

}  // namespace wearable