#include <epictuner/sdk/ui.hpp>
#include <epictuner/sdk/tune.hpp>
#include <chrono>
#include <cstdlib>
#include <optional>
#include <stdexcept>
#include <thread>

using namespace et::sdk;
// Test-only: explicitly loaded in a disposable project. No fault runs on startup.
class Fault : public Plugin {
    std::optional<Context> host;
    Ui ui;
    Reads project, channels;
    Tune tune;
    uint64_t generation = 0;
    bool slow = false;
    ReadCallback events{data, this}, reply{readDone, this};
    static et_result ET_CALL readDone(void* p, const et_read_reply* r) {
        static_cast<Fault*>(p)->host->log("fault read response=" + std::to_string(r->code));
        return ET_OK;
    }
    static et_result ET_CALL data(void* p, const et_read_reply* r) {
        auto& self = *static_cast<Fault*>(p);
        if (r->event == ET_READ_EVENT_PROJECT) self.generation = r->project_generation;
        if (r->event == ET_READ_EVENT_SAMPLES && self.slow) {
            self.host->log("fault sample gaps=" + std::to_string(r->gaps));
            // 32 rows per delivery cannot keep up with a 100 Hz source at this
            // delay. The host must report ring overflow while polling continues.
            std::this_thread::sleep_for(std::chrono::milliseconds(1200));
        }
        return ET_OK;
    }
    et_async_options completion{sizeof(et_async_options), ET_OPERATION_TIMEOUT_MS,
                               0, 0, this, completed};
    static void ET_CALL completed(void* p, const et_completion* r) noexcept {
        static_cast<Fault*>(p)->host->log("fault response=" + std::to_string(r->code));
    }
    static et_result ET_CALL input(void* p, const et_ui_event* e) {
        if (e->type != ET_UI_CLICK) return ET_OK;
        return static_cast<Fault*>(p)->command({e->node.data, e->node.size});
    }
    et_result command(std::string_view text) {
        if (text == "crash") std::_Exit(71);
        if (text == "hang") for (;;) std::this_thread::sleep_for(std::chrono::seconds(1));
        if (text == "throw") throw std::runtime_error("Synthetic callback exception");
        if (text == "flood") {
            unsigned rejected = 0;
            for (unsigned i = 0; i < 10000; ++i)
                if (host->log("bounded fault flood") == ET_ERROR_LIMIT) ++rejected;
            return rejected >= 9900 ? ET_OK : ET_ERROR_STATE;
        }
        if (text == "wrong-thread") {
            et_result result = ET_OK;
            std::thread thread([&] { result = host->log("must not enqueue"); });
            thread.join();
            return result == ET_ERROR_STATE ? ET_OK : ET_ERROR_STATE;
        }
        if (text == "invalid-handle")
            return ui.action({1, 0, 0xffffffffu, 1, ET_HANDLE_PANEL, 0}, ET_UI_FOCUS, 0, &completion);
        if (text == "stale-write") {
            auto request = Reads::make(ET_TUNE_APPLY, generation + 1);
            request.handle = {1, generation + 1, 1, 1, ET_HANDLE_PROPOSAL, 0};
            return tune.request(request, reply);
        }
        if (text == "slow-consumer") {
            slow = true;
            auto request = Reads::make(ET_READ_SUBSCRIBE, generation);
            auto rpm = string("rpm");
            request.channels = &rpm;
            request.channel_count = 1;
            return channels.request(request, reply);
        }
        if (text == "malformed-model") {
            PanelBuilder bad("org.epictuner.examples.fault.bad", "Invalid tree");
            bad.add(ET_UI_COLUMN, "root");
            bad.add(ET_UI_LABEL, "orphan", "missing-parent").text(ET_UI_TITLE, "Must be rejected");
            return ui.define(bad, &completion);
        }
        return ET_OK;
    }
public:
    static constexpr std::string_view id = "org.epictuner.examples.fault";
    static constexpr uint64_t capabilities = ET_CAP_UI | ET_CAP_LOG | ET_CAP_EVENTS |
        ET_CAP_PROJECT_READ | ET_CAP_CHANNEL_READ | ET_CAP_TUNE_READ | ET_CAP_TUNE_APPLY;
    et_result start(Context& context) {
        host = context;
        const auto result = ui.connect(context, input, this);
        if (result != ET_OK) return result;
        if (project.connect(context, ET_SERVICE_PROJECT) != ET_OK ||
            channels.connect(context, ET_SERVICE_CHANNELS) != ET_OK ||
            tune.connect(context) != ET_OK) return ET_ERROR_UNSUPPORTED;
        project.listen(events);
        PanelBuilder panel("org.epictuner.examples.fault.main", "Fault injection (test only)");
        panel.definition.flags = 0;
        panel.definition.menu = panel.keep("Tests/Fault injection");
        panel.add(ET_UI_COLUMN, "root");
        panel.add(ET_UI_BANNER, "warning", "root").text(ET_UI_TITLE,
            "Use a disposable project. Crash and Hang terminate this plugin worker. Restart manually in Plugins.");
        for (auto action : {"crash", "hang", "throw", "flood", "wrong-thread", "invalid-handle", "malformed-model", "stale-write", "slow-consumer"})
            panel.add(ET_UI_BUTTON, action, "root").text(ET_UI_TITLE, action);
        return ui.define(panel, &completion);
    }
    et_result onEvent(const et_event& event) { return command({event.text.data, event.text.size}); }
};
ET_PLUGIN(Fault)
