#include "board.h"
#include "device_status.h"
#include "ui_state.h"
#include "agent_navigator.h"
#include "cloud_link_state.h"
#include "watch_status.h"
#include "websocket_text_receiver.h"
#include "cJSON.h"
#include <atomic>
#include <cassert>
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <deque>
#include <string>
#include <vector>

// Hardware and SDK substitute. Production event callbacks/dispatch are included
// unchanged; ESP-IDF firmware build verifies the actual 1.8.0 ABI separately.
using esp_event_base_t = const char *;
using esp_websocket_client_handle_t = void *;
enum esp_websocket_error_type_t { WEBSOCKET_ERROR_TYPE_NONE, WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT,
    WEBSOCKET_ERROR_TYPE_PONG_TIMEOUT, WEBSOCKET_ERROR_TYPE_HANDSHAKE, WEBSOCKET_ERROR_TYPE_SERVER_CLOSE };
struct esp_websocket_error_codes_t {
    int esp_tls_last_esp_err=0, esp_tls_stack_err=0, esp_transport_sock_errno=0,
        esp_ws_handshake_status_code=0;
    esp_websocket_error_type_t error_type=WEBSOCKET_ERROR_TYPE_NONE;
};
struct esp_websocket_event_data_t {
    const char *data_ptr=nullptr; int data_len=0; bool fin=true; uint8_t op_code=1;
    int payload_len=0, payload_offset=0;
    esp_websocket_error_codes_t error_handle{}; int close_status_code=0;
};
enum { WEBSOCKET_EVENT_BEFORE_CONNECT, WEBSOCKET_EVENT_CONNECTED, WEBSOCKET_EVENT_DISCONNECTED,
       WEBSOCKET_EVENT_CLOSED, WEBSOCKET_EVENT_DATA, WEBSOCKET_EVENT_ERROR };
constexpr int ESP_OK=0, ESP_ERR_INVALID_ARG=0x102, MALLOC_CAP_INTERNAL=1, MALLOC_CAP_DMA=2;
constexpr int CLOUD_RECONNECT_TIMEOUT_MS=1500, VOICE_SEND_TIMEOUT_MS=2000;
constexpr const char *TAG="host", *CONFIG_DEVICE_LAB_DEVICE_TOKEN="synthetic-only",
    *DEVICE_LAB_FIRMWARE_VERSION="0.6.0";
uint64_t now_ms=1000;
int64_t esp_timer_get_time() { return now_ms*1000; }
struct wifi_ap_record_t { int rssi=-61; };
int esp_wifi_sta_get_ap_info(wifi_ap_record_t *) { return ESP_OK; }
unsigned heap_caps_get_free_size(int) { return 100000; }
unsigned heap_caps_get_largest_free_block(int) { return 50000; }
int pdMS_TO_TICKS(int value) { return value; }
std::vector<std::string> logs, sent;
void host_log(const char *, const char *format, ...) {
    char text[2048]; va_list args; va_start(args,format); vsnprintf(text,sizeof(text),format,args);
    va_end(args); logs.emplace_back(text);
}
#define ESP_LOGI host_log
#define ESP_LOGD host_log
#define ESP_LOGW host_log
#define ESP_LOGE host_log
int esp_websocket_client_send_text(void *, const char *data, size_t size, int) {
    sent.emplace_back(data,size); return size;
}
const char *board_slug() { return "synthetic"; }
void log_link_health(const char *, size_t, unsigned, int64_t) {}
std::atomic<bool> s_connected{false}, s_ready{false};
std::atomic<uint32_t> s_generation{0};
CloudLinkState s_link;
struct { wearable::HostState host=wearable::HostState::Offline; bool has_host_state=false; } s_server;
wearable::WallClock s_clock;
wearable::AgentNavigator s_nav;
wearable::UiState s_ui;
wearable::LoadedPreferences s_saved_preferences;
struct RtcMock {unsigned writes=0;uint64_t utc=0;int write_utc_ms(uint64_t value) {++writes;utc=value;return ESP_OK;}} s_rtc;
int preferences_save(wearable::LoadedPreferences&,const wearable::DevicePreferences&) {return ESP_OK;}
const char* esp_err_to_name(int value) {return value==ESP_OK?"ESP_OK":"ERROR";}
esp_websocket_client_handle_t s_client=reinterpret_cast<void *>(1);
char s_device_id[24]="host-test";
WebSocketTextReceiver s_text_receiver;
int64_t s_rx_started_us=0, s_last_rx_trace_us=0;
unsigned s_rx_parts=0;

enum class InputModal { NONE, VOICE, ERROR, SETTINGS };
InputModal s_input_modal=InputModal::NONE;
std::string s_voice_phase, s_modal_error, s_notified_approval, s_settings_phase;
uint64_t s_voice_serial=1;
bool s_cloud_connected=false, s_message_visible=false;
std::deque<std::string> s_control_queue;
struct { unsigned disconnects=0; void disconnect() { ++disconnects; } } s_approval;
struct { std::string id; } s_approval_action;
std::string agent="pi", session="s", name="Selected Pi", state="RUNNING", final_text="previous final";
double elapsed_seconds=12;
void lock_display() {}
void unlock_display() {}
void stop_speech(bool = true) {}
void cancel_input(bool, const char *) { s_input_modal=InputModal::NONE; }
void draw_status() {}
#include "board_link.inc"
bool board_accept_target(const char*) {return true;}
void board_approval_request(const char *, const char *, const char *, const char *, unsigned) {}
void board_approval_cancelled(const char *, const char *) {}
void board_set_server_snapshot(const board_server_snapshot_t *) {}
void board_select_server_target() { session="server"; }
void board_set_pi_snapshot(const board_pi_snapshot_t *value) {
    agent=value->agent; session=value->session_id; name=value->name; state=value->state;
    final_text=value->final_text?value->final_text:""; elapsed_seconds=value->elapsed_seconds;
}
void board_notify_pi(bool) {}
void board_agent_activity(const char *, const char *, const char *, const char *, const char *) {}
void board_control_message(const char *) {}
void board_show_message(const char *, const char *) {}
void board_vibrate(uint32_t) {}
#include "cloud.inc"

void event(int kind, esp_websocket_event_data_t value={}) { websocket_event(nullptr,nullptr,kind,&value); }
void receive(const char *wire) {
    esp_websocket_event_data_t data{}; data.data_ptr=wire; data.data_len=data.payload_len=std::strlen(wire);
    event(WEBSOCKET_EVENT_DATA,data);
}
int main() {
    char storage[16385]; s_text_receiver.set_storage(storage,16384);
    esp_websocket_event_data_t unused{};
    unused.error_handle.esp_tls_last_esp_err=12345;
    unused.error_handle.esp_tls_stack_err=23456;
    unused.error_handle.esp_transport_sock_errno=34567;
    log_connection("connected",&unused);
    assert(logs.back().find("esp_err=0x0 tls_err=0 sock_errno=0")!=std::string::npos);
    unused.error_handle.error_type=WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT;
    log_connection("disconnected",&unused);
    assert(logs.back().find("tls_err=23456 sock_errno=34567")!=std::string::npos);
    event(WEBSOCKET_EVENT_BEFORE_CONNECT); event(WEBSOCKET_EVENT_CONNECTED);
    assert(s_generation==1 && sent.size()==1 && !s_cloud_connected);
    assert(sent[0].find("\"type\":\"hello\"")!=std::string::npos);
    receive("{\"type\":\"hello_ack\"}"); assert(!s_cloud_connected);
    receive("{\"type\":\"agent_state\",\"agent\":\"pi\",\"session_id\":\"s\",\"name\":\"Selected Pi\",\"state\":\"RUNNING\",\"elapsed_seconds\":12}");
    assert(s_cloud_connected && s_ready && state=="RUNNING");
    receive("{\"type\":\"hello_ack\",\"server_time_ms\":1791072000000,\"timezone_offset_minutes\":480}");
    assert(s_clock.utc_ms(now_ms)==1791072000000);
    assert(s_clock.local_ms(now_ms)==1791072000000+480*60000LL && s_rtc.writes==1);
    receive("{\"type\":\"hello_ack\",\"server_time_ms\":\"invalid\"}");
    assert(s_clock.utc_ms(now_ms)==1791072000000);
    receive("{\"type\":\"hello_ack\",\"server_time_ms\":1}");
    assert(s_clock.utc_ms(now_ms)==1791072000000);
    final_text="previous final"; s_control_queue.push_back("old approval"); s_approval_action.id="old";
    s_input_modal=InputModal::VOICE; s_voice_phase="RECORDING";
    s_server.host=wearable::HostState::Busy;s_server.has_host_state=true;
    now_ms=2000;
    esp_websocket_event_data_t error{}; error.error_handle.error_type=WEBSOCKET_ERROR_TYPE_PONG_TIMEOUT;
    event(WEBSOCKET_EVENT_ERROR,error); event(WEBSOCKET_EVENT_DISCONNECTED,error);
    assert(!s_cloud_connected && !s_ready && s_generation==1);
    assert(s_server.host==wearable::HostState::Offline && !s_server.has_host_state);
    assert(session=="s" && name=="Selected Pi" && state=="RUNNING" && final_text=="previous final");
    assert(s_control_queue.empty() && s_approval_action.id.empty());
    assert(s_input_modal==InputModal::ERROR && s_modal_error=="Voice interrupted\nTry again");
    assert(!cloud_operation_current(1,1,false));
    now_ms=2500; event(WEBSOCKET_EVENT_CLOSED,error);
    assert(s_link.outage_ms==2000 && s_approval.disconnects==1); // duplicate close is inert
    now_ms=3500; event(WEBSOCKET_EVENT_BEFORE_CONNECT); event(WEBSOCKET_EVENT_CONNECTED);
    assert(s_generation==2 && sent.size()==2 && !s_cloud_connected);
    assert(s_server.host==wearable::HostState::Offline && !s_server.has_host_state);
    receive("{\"type\":\"hello_ack\"}"); assert(!s_cloud_connected);
    receive("{\"type\":\"agent_state\",\"agent\":\"pi\",\"session_id\":\"s\",\"name\":\"Selected Pi\",\"state\":\"DONE\",\"final_text\":\"离线完成。\",\"elapsed_seconds\":15}");
    assert(s_cloud_connected && state=="DONE" && final_text=="离线完成。" && elapsed_seconds==15);
    assert(!cloud_operation_current(1,2,true) && cloud_operation_current(2,2,true));
    assert(!send_json(cJSON_CreateObject(),1) && sent.size()==2);
    // A late failure for an old capture cannot cancel a new capture.
    s_voice_serial=2; s_input_modal=InputModal::VOICE; board_voice_interrupted(1);
    assert(s_input_modal==InputModal::VOICE); board_voice_interrupted(2);
    assert(s_input_modal==InputModal::ERROR);
    // Device/Display local settings are still usable across a transient link loss.
    s_input_modal=InputModal::SETTINGS;s_settings_phase="MENU";s_cloud_connected=true;
    board_set_cloud_status(false);
    assert(s_input_modal==InputModal::SETTINGS);
    s_settings_phase="MODEL";board_set_cloud_status(false);assert(s_input_modal==InputModal::NONE);
    for (const auto &log:logs) {
        assert(log.find("synthetic-only")==std::string::npos);
        assert(log.find("离线完成")==std::string::npos);
    }
    std::puts("Network callbacks / new hello / generation / authoritative DONE / Voice interruption / stale operation PASS (host)");
}
