// Wi-Fi network lifecycle: portal candidate recovery (B4) and Human Gate
// preemption (item 12).
//
// These are the end-to-end behaviours Codex observed on hardware, expressed as
// a host model of the firmware's ownership + provisioning interaction. The
// firmware binding (wifi_manager.cpp) drives the same `ConnectionOwnership`
// machine, so a regression in the policy shows up here without hardware.
#include "wifi_connection_ownership.h"
#include "wifi_selection.h"

#include <cassert>
#include <cstdio>
#include <cstring>

using namespace wearable;

// A minimal model of the firmware's provisioning mode + ownership, mirroring
// wifi_manager's variables and transitions.
struct FirmwareModel {
    ProvisioningMode provisioning = ProvisioningMode::Normal;
    ConnectionOwnership own;
    char current_ssid[WIFI_SSID_MAX + 1] = {};
    WifiLinkState state = WifiLinkState::NoKnownWifi;
    int mark_success_calls = 0;
    int ap_starts = 0, ap_stops = 0;

    void start_provisioning() {
        const auto next = request_start_access_point(provisioning, true);
        provisioning = next;
        const bool ok = true;
        provisioning = access_point_ready(provisioning, ok);
        if (provisioning == ProvisioningMode::Provisioning) ++ap_starts;
    }
    void stop_provisioning() {
        if (provisioning != ProvisioningMode::Normal) ++ap_stops;
        provisioning = exit_provisioning(provisioning);
    }
    bool setup_running() const { return access_point_should_run(provisioning); }

    // Station loop reachable only when no candidate owns the interface.
    void station_tick(const char *known) {
        if (own.owner() == ConnectionOwner::Candidate) return;
        if (own.station_steady()) return;  // B3: steady link is left alone
        const auto attempt = own.station_begin(known);
        if (!attempt.valid) return;
        const auto outcome = own.station_resolve(attempt, true, known);
        if (outcome == AttemptOutcome::Accepted) {
            std::strncpy(current_ssid, known, sizeof(current_ssid) - 1);
            ++mark_success_calls;
            state = WifiLinkState::Connected;
        } else {
            own.station_idle();
            state = WifiLinkState::Offline;
        }
    }

    // A portal/USB candidate submit.
    CandidateResult test_candidate(const char *ssid, bool succeeds) {
        const auto attempt = own.take_candidate(ssid);
        AttemptDirective directive;
        const auto outcome = own.candidate_resolve(attempt, succeeds, succeeds ? ssid : "", directive);
        if (outcome == AttemptOutcome::Accepted) {
            std::strncpy(current_ssid, ssid, sizeof(current_ssid) - 1);
            ++mark_success_calls;
            state = WifiLinkState::Connected;
            return CandidateResult::Success;
        }
        return CandidateResult::Failed;
    }

    // Human Gate preemption (item 12).
    void preempt_for_approval() {
        own.cancel_candidate();
        if (provisioning != ProvisioningMode::Normal) stop_provisioning();
    }
};

// B4: a rejected portal candidate must not strand the device. The SoftAP stays
// up for retry, the candidate is not persisted, and the station automatically
// reconnects a known profile.
static void portal_candidate_failure_auto_recovery() {
    FirmwareModel fw;
    // Known network A is already connected.
    fw.station_tick("A");
    assert(fw.state == WifiLinkState::Connected);
    assert(std::strcmp(fw.current_ssid, "A") == 0);
    const int marks_after_a = fw.mark_success_calls;

    // User opens Wi-Fi Setup and submits a wrong-password candidate C.
    fw.start_provisioning();
    assert(fw.setup_running());
    const auto result = fw.test_candidate("C", /*succeeds=*/false);
    assert(result == CandidateResult::Failed);
    // Candidate not persisted and not adopted.
    assert(std::strcmp(fw.current_ssid, "A") == 0);
    assert(fw.mark_success_calls == marks_after_a);
    // Portal / SoftAP remains running for another attempt (B4).
    assert(fw.setup_running());
    assert(fw.provisioning == ProvisioningMode::Provisioning);

    // Station ownership was released: the loop reconnects A automatically,
    // WITHOUT the user having to Hold B to exit setup.
    for (int i = 0; i < 5; ++i) fw.station_tick("A");
    assert(fw.state == WifiLinkState::Connected);
    assert(std::strcmp(fw.current_ssid, "A") == 0);

    // A second, successful submission takes ownership again and persists.
    const auto ok = fw.test_candidate("B", /*succeeds=*/true);
    assert(ok == CandidateResult::Success);
    assert(std::strcmp(fw.current_ssid, "B") == 0);
    assert(fw.mark_success_calls > marks_after_a);

    std::puts("B4 portal candidate failure: not persisted, AP stays up, "
              "station auto-reconnects A PASS");
}

// B3: once connected, many station ticks must not reconnect or re-mark success.
static void steady_connection_is_not_reestablished() {
    FirmwareModel fw;
    fw.station_tick("A");
    assert(fw.state == WifiLinkState::Connected);
    const int marks = fw.mark_success_calls;
    for (int i = 0; i < 100; ++i) fw.station_tick("A");
    assert(fw.mark_success_calls == marks);           // no NVS churn
    assert(std::strcmp(fw.current_ssid, "A") == 0);   // no flip-flop
    std::puts("B3 steady connection: 100 ticks, no reconnect/mark_success PASS");
}

// item 12: an Approval arriving during provisioning stops the AP and invalidates
// the in-flight candidate; a late candidate result never commits.
static void approval_preempts_provisioning() {
    FirmwareModel fw;
    fw.start_provisioning();
    assert(fw.setup_running());
    // A candidate is mid-flight when the Human Gate arrives.
    const auto attempt = fw.own.take_candidate("C");
    fw.preempt_for_approval();
    assert(!fw.setup_running());                 // AP stopped
    assert(fw.provisioning == ProvisioningMode::Normal);

    // The candidate's late result is Stale and must not commit.
    AttemptDirective directive;
    assert(fw.own.candidate_resolve(attempt, true, "C", directive) == AttemptOutcome::Stale);
    assert(!directive.persist);
    assert(fw.current_ssid[0] == '\0');
    std::puts("item 12 approval preemption: AP stopped, late candidate ignored PASS");
}

// The same, for the Portal success path: a candidate that resolves Success
// while ownership was preempted must not be persisted by the late result.
static void preemption_wins_even_if_candidate_would_succeed() {
    FirmwareModel fw;
    fw.start_provisioning();
    const auto attempt = fw.own.take_candidate("Good");
    fw.preempt_for_approval();
    AttemptDirective directive;
    assert(fw.own.candidate_resolve(attempt, true, "Good", directive) == AttemptOutcome::Stale);
    assert(!directive.persist);
    assert(fw.current_ssid[0] == '\0');
    std::puts("preemption beats a candidate that would otherwise succeed PASS");
}

int main() {
    portal_candidate_failure_auto_recovery();
    steady_connection_is_not_reestablished();
    approval_preempts_provisioning();
    preemption_wins_even_if_candidate_would_succeed();
    std::puts("Wi-Fi network lifecycle: B3/B4 + Approval/Provisioning ownership PASS (host)");
    return 0;
}