#include "device_preferences_nvs.h"
#include "nvs.h"
#include <cassert>
#include <cstdio>
#include <cstring>
#include <map>
#include <string>
#include <vector>
using namespace wearable;
std::map<std::string,std::vector<uint8_t>> disk,pending;
bool fail_set=false,fail_commit=false,fail_read=false;
unsigned writes=0,commits=0;
esp_err_t nvs_open(const char* space,int mode,nvs_handle_t* h) {
    assert(std::strcmp(space,"wearable")==0); if(disk.empty() && mode==NVS_READONLY) return ESP_ERR_NVS_NOT_FOUND;
    *h=1; return ESP_OK;
}
void nvs_close(nvs_handle_t) {pending.clear();}
esp_err_t nvs_get_blob(nvs_handle_t,const char* key,void* out,size_t* size) {
    if(fail_read) return ESP_FAIL;
    auto it=disk.find(key);if(it==disk.end()) return ESP_ERR_NVS_NOT_FOUND;
    if(*size<it->second.size()) return ESP_ERR_NVS_INVALID_LENGTH;
    *size=it->second.size();std::memcpy(out,it->second.data(),*size);return ESP_OK;
}
esp_err_t nvs_set_blob(nvs_handle_t,const char* key,const void* data,size_t size) {
    ++writes;if(fail_set) return ESP_FAIL;
    pending[key]=std::vector<uint8_t>((const uint8_t*)data,(const uint8_t*)data+size);return ESP_OK;
}
esp_err_t nvs_commit(nvs_handle_t) {
    ++commits;if(fail_commit) return ESP_FAIL;
    for(auto& entry:pending) { disk[entry.first]=entry.second; }
    return ESP_OK;
}
int main() {
    assert(crc32((const uint8_t*)"123456789",9)==0xcbf43926);
    auto p=preferences_load();assert(p.slot==-1 && !p.read_only && p.value.home_screen==Home::Watch);
    assert(p.value.timezone_offset_minutes==0);
    assert(panel_value(80)==204 && panel_value(10)==26 && panel_value(100)==255);
    assert(panel_value(0)==26 && panel_value(255)==255); // Hardware edge clamps invalid input.
    auto value=p.value;value.brightness=30;
    assert(preferences_save(p,value)==ESP_OK && p.slot==0 && p.generation==1);
    auto first=disk["prefs_a"];
    value.home_screen=Home::Watch;
    assert(preferences_save(p,value)==ESP_OK && p.slot==1 && p.generation==2);
    auto reboot=preferences_load();assert(reboot.generation==2 && reboot.value.home_screen==Home::Watch);
    disk["prefs_b"][10]^=1;
    reboot=preferences_load();assert(reboot.slot==0 && reboot.value.brightness==30);
    disk["prefs_a"][0]=0;
    reboot=preferences_load();assert(reboot.slot==-1 && reboot.value.brightness==80);
    disk.clear();disk["prefs_a"]=first;p=preferences_load();
    value.brightness=90;fail_commit=true;
    assert(preferences_save(p,value)==ESP_FAIL && p.generation==1 && p.value.brightness==30);
    assert(disk["prefs_a"]==first && preferences_load().value.brightness==30);
    fail_commit=false;fail_set=true;assert(preferences_save(p,value)==ESP_FAIL && disk["prefs_a"]==first);fail_set=false;
    auto future=first;future[4]=6;put32(future.data()+future.size()-4,crc32(future.data(),future.size()-4));disk["prefs_b"]=future;
    p=preferences_load();assert(p.read_only && p.slot==0 && p.value.brightness==30);
    const auto before=writes;assert(preferences_save(p,value)==ESP_ERR_INVALID_STATE && writes==before);
    // Future envelope larger than the original prototype's 64-byte limit is protected.
    future.resize(114);future[5]=100;put32(future.data()+110,crc32(future.data(),110));disk["prefs_b"]=future;
    assert(preferences_load().read_only);
    disk.erase("prefs_b");fail_read=true;assert(preferences_load().read_only);fail_read=false;
    Blob max{};encode(value,UINT32_MAX,max);disk["prefs_a"]={max.begin(),max.end()};assert(preferences_load().read_only);
    value.brightness=0;assert(!encode(value,1,max));value.brightness=80;
    value.theme=Theme(9);assert(!encode(value,1,max));value.theme=Theme::Dark;
    value.timezone_offset_minutes=841;assert(!encode(value,1,max));value.timezone_offset_minutes=480;
    assert(encode(value,1,max));DevicePreferences roundtrip;uint32_t generation=0;
    assert(decode(max.data(),max.size(),roundtrip,generation)==Decode::Current);
    assert(roundtrip.timezone_offset_minutes==480 && generation==1);
    assert(decode(max.data(),max.size()-1,roundtrip,generation)==Decode::Invalid); // truncated
    // Synthesized v1 migration fixture; no production 0.5 format was persisted.
    encode(value,7,max);std::vector<uint8_t> old(max.begin(),max.begin()+15);old[4]=1;old[5]=5;
    old.resize(19);put32(old.data()+15,crc32(old.data(),15));disk["prefs_a"]=old;
    p=preferences_load();assert(p.migration_needed && p.generation==7);
    assert(preferences_save(p,p.value)==ESP_OK && p.generation==8 && !p.migration_needed);
    // Every older schema preserves the existing Confirm behavior, and v1-v4
    // default the new Touch preference to On.
    for(unsigned version=1;version<=4;++version) {
        encode(value,10,max);const size_t payload=version==1?5:version==2?7:version==3?9:10;
        old.assign(max.begin(),max.begin()+10+payload);old[4]=version;old[5]=payload;
        old.resize(payload+14);put32(old.data()+old.size()-4,crc32(old.data(),old.size()-4));
        assert(decode(old.data(),old.size(),roundtrip,generation)==Decode::Migrated);
        assert(roundtrip.voice_send_mode==VoiceSendMode::Confirm);
        assert(roundtrip.touch_enabled==1);
    }
    // v5 persists Touch Off across a reboot, and an explicit Off round-trips.
    disk.clear();p=preferences_load();value=p.value;value.touch_enabled=0;
    assert(preferences_save(p,value)==ESP_OK);
    assert(preferences_load().value.touch_enabled==0);
    value.touch_enabled=2;assert(!encode(value,1,max));value.touch_enabled=1;
    assert(decode(max.data(),max.size(),roundtrip,generation)==Decode::Current);
    disk.clear();p=preferences_load();value=p.value;value.voice_send_mode=VoiceSendMode::Direct;
    assert(preferences_save(p,value)==ESP_OK && preferences_load().value.voice_send_mode==VoiceSendMode::Direct);
    value.voice_send_mode=VoiceSendMode::Confirm;
    assert(preferences_save(p,value)==ESP_OK && preferences_load().value.voice_send_mode==VoiceSendMode::Confirm);
    std::puts("Wearable preferences/NVS failure and reboot recovery PASS");
}
